Навигация по связанным сущностям в рамках ADO.NET Data Services

В последнее время, когда данные все чаще представляют в виде объектных моделей время от времени появляются разговоры о навигации по связанным сущностям. Т.е. с точки зрения модели данных есть две коллекции, сущности которых ссылаются друг на друга, и технологии, которые позволяют работать с моделями данных так или иначе стараются решить проблемы навигации/перехода от одной сущности к другой.

Сейчас я бы хотел поговорить о том, каким образом это реализуется в технологии ADO.NET Data Services. ADO.NET Data Services – это платформа, которая позволяет предоставить доступ к данным в Web по протоколу HTTP в REST-стиле.

Соответственно, когда на клиентской стороне мы получаем доступ к какой-либо модели данных, полученной от ADO.NET Data Services, задача навигации между сущностями кажется совершенно нормальной. ADO.NET Data Services предлагает два варианта решения этой задачи:

  • Использовать ключевое слово $expand при выполнении запроса.
  • Использовать метод клиентского класса LoadProperty().

Давайте посмотрим в чем разница.

При использовании ключевого слова $expand мы явно при выполнении запроса указываем какие связанные контейнеры объектов хотим получить. Например, у нас есть таблица Customers и связанная с ней таблица Orders. В этом случае мы можем осуществить следующий запрос:

http://.../Service.svc/Customers/?$expand=Orders

В этом случае в рамках одного запроса к серверу (round-trip) будет получена коллекция Customers (например, в виде AtomPub) и для каждой сущности Customer будут представлены объекты Order. Таким образом, на клиентской стороне после выполнения такого запроса и навигации от объекта Customer к объектам Order запросов больше выполняться не будет – все нужные данные уже загружены. Для того, чтобы клиентский прокси-класс включил в генерируемый URI ключевое слово Expand необходимо воспользоваться одноименным extension-методом. Вот как может выглядеть код клиентского приложения:

var context = new NorthwindService.NorthwindEntities(new Uri(@"http://../Service.svc"));
var query = context.Customers.Expand("Orders");
foreach (var customer in query)
{
    Console.WriteLine(customer.CompanyName);
    foreach (var order in customer.Orders)
    {
        Console.Write(order.OrderID);
    }
    Console.WriteLine();
}

Однако, такой подход хорош только тогда, когда мы точно знаем что данные из связанной коллекции нам будут нужны. Если при перемещенни по коллекции Customers данные о заказах (Orders) нам нужны только в небольшом количестве случаев, то возможно нету большого смысла загружать все данные из связанной коллекции Orders. В этом случае мы можем прибегнуть ко второму способу – использованию метода LoadProperty().

При вызове метода LoadProperty() будет осуществляться отдельный round-trip к сервису для получения только тех объектов Orders, которые связаны с текущим объектом Customer. Таким образом, мы можем не использовать Expand для нашего запроса, а тогда, когда нам будут необходимы данные из связанной коллекции мы можем обратиться к методу LoadProperty() и после исполнения запроса получить необходимые данные. Вот как может выглядеть код клиентского приложения в этом случае:

var context = new NorthwindService.NorthwindEntities(new Uri(@"http://../Service.svc"));
var query = context.Customers; //.Expand("Orders")
foreach (var customer in query)
{
    Console.WriteLine(customer.CompanyName);

    // определенные условия   
    if (..)
    {
        context.LoadProperty(customer, "Orders");

        foreach (var order in customer.Orders)
        {
            Console.Write(order.OrderID);
            Console.Write(" ");
        }
    }
    Console.WriteLine();
}

Понятно, что оба варианта по своему хороши. Если мы точно знаем что каждый раз будем работать со связанной коллекцией, то нет смысла генерировать большое количество запросов к сервису — в этом случае проще получить все за один запрос; в случае если данные из связанных коллекций нужны далеко не всегда, то в этом случае можно подгружать их только тогда, когда это необходимо и не генерировать огромный ответ, который включает по сути две коллекции, хотя это вовсе не обзятельно.