2015-12-07 2 views
2

У меня есть настройка, где я получаю услугу OAata WebApi, которая возвращает: Клиенты. Код для возвращения клиентов является:

public IHttpActionResult GetCustomers(ODataQueryOptions<Customer> queryOptions) 
{ 
    return Ok(context.Customers.Where(i => i.IsActive).AsQueryable()); 
} 

Так метод GetCustomers возвращает IQuerable результат всех активных клиентов. Для целей истории мы оставляем всех клиентов в базе данных, но когда клиент удален, мы устанавливаем поле IsActive в значение false.

Настройка OData создается с помощью простого builder.EntitySet для создания Url для объектов.

EntitySetConfiguration<Customer> customers = builder.EntitySet<Customer>("customers"); 

Это работает безупречно. У меня есть угловой интерфейс, который использует вызовы $ http для получения клиентов и т. Д.

Однако клиент может содержать связанные контакты в базе данных. Для того, чтобы получить контакты в угловом Frontend, я использую $ расширить функциональность OData:

odata/customers?$expand=contacts 

Это также отлично работает. Я получаю клиентов со всеми связанными контактами. Однако, как вы догадались, мне хотелось бы получить только те контакты, которые должны быть возвращены IsActive. И функциональность IQueryable возвращает мне все результаты.

Я понимаю, что могу использовать отдельный вызов Odata для получения контактов, но я действительно хотел бы использовать функции $ expand для получения всех данных за один вызов. Я знаю, что также могу выполнять фильтрацию на стороне клиента (с помощью фильтра $). Но я хотел бы установить это правильно в части WebApi, поэтому клиенту не нужно заботиться о фильтрации неактивных результатов.

Я не могу понять, как это сделать правильно. Может ли кто-нибудь помочь мне встать на правильный путь?

+0

Вы можете сделать это с помощью Eager loading? https://msdn.microsoft.com/en-us/data/jj574232.aspx?f=255&MSPPError=-2147217396 –

+0

Александр, это может быть решение, однако, я не хотел бы получать контакты каждый раз, когда вы получаете клиентов. –

+0

Не могли бы вы попытаться вернуть 'IQueryable '? – csharpfolk

ответ

4

EntityFramework.DynamicFilters является один из лучших инструментов для Entity Framework, который я знаю. Он прыгнул в промежуток часто запрашиваемых, но до EF6 никогда не реализовывала функцию фильтрованных Incude с. Он опирается на API перехвата EF и делает тяжелый подъем модификационных выражений при экспонировании очень простого интерфейса.

В вашем случае, что вы можете сделать что-то вроде этого:

using EntityFramework.DynamicFilters; 

// In OnModelCreating (DbContext) 
modelBuilder.Filter("CustomerActive", (Customers c) => c.IsActive); 

Это все! Теперь везде, где запрашиваются Customers, будь то напрямую, через свойства навигации или в Include, предикат будет добавлен в запрос.

Вы хотите всех клиентов? Вы можете просто включить фильтр от каждого экземпляра контекста, делая

context.DisableFilter("CustomerActive"); 

Там только один глюк (или предостережение) я обнаружил до сих пор. Если есть две сущности, Parent и Child и есть фильтр на Parent, которая не возвращает никаких записей, то этот запрос ...

context.Children.Include(c => c.Parent) 

... ничего не возвращают. Однако из формы запроса я ожидаю, что он вернет Child сущности с пустыми родителями.

Это происходит потому, что в SQL есть INNER JOIN между Parent и Child и предикатом Parent, оценивающим в false. OUTER JOIN даст ожидаемое поведение, но, конечно, мы не можем требовать от этой библиотеки , что smart.

0

Одним из возможных решений является добавление Просмотров для представления данных, которые вы действительно хотите выставить.

Вы можете иметь клиентов и КонтактнаяПросмотров которые только отфильтрованные версии исходной таблицы.

Назад на стороне C#, ваши модели могут напрямую ссылаться на Просмотров как если бы они были таблицами.

Приятно, что они будут обрабатываться так же, как и таблицы, все ленивые загрузки, свойства навигации и фильтрация на стороне базы данных будут по-прежнему работать так, как если бы вы ссылались на исходные таблицы.

+0

Это может быть жизнеспособный вариант использования представлений, но мне не нравится общее ощущение идеи использования представлений в коде Entity Framework. Я думаю, что использование решения от @GertArnold лучше подходит для моего вопроса. –

2

модель данных:

public class Customer 
{ 
    public int Id { get; set; } 
    public bool IsActive { get; set; } 
    public ICollection<Contact> Contacts { get; set; } 
} 

public class Contact 
{ 
    public int Id { get; set; } 
    public bool IsActive { get; set; } 
} 

контроллера с консервированными данными:

public class CustomersController : ODataController 
{ 
    private List<Customer> customers = new List<Customer> 
    { 
     new Customer { Id = 1, IsActive = false }, 
     new Customer { Id = 2, IsActive = true, 
      Contacts = new List<Contact> 
      { 
       new Contact { Id = 101, IsActive = true }, 
       new Contact { Id = 102, IsActive = false }, 
       new Contact { Id = 103, IsActive = true }, 
      } 
     } 
    }; 

    [EnableQuery] 
    public IHttpActionResult Get() 
    { 
     return Ok(customers.Where(c => c.IsActive).AsQueryable()); 
    } 
} 

Обратите внимание, что один Клиент активен, и что Клиент имеют 2 (из 3) активных контактов.

Наконец, настроить службу OData:

public static class WebApiConfig 
{ 
    public static void Register(HttpConfiguration config) 
    { 
     var builder = new ODataConventionModelBuilder(); 
     builder.EntitySet<Customer>("customers"); 

     config.MapODataServiceRoute(
      routeName: "OData", 
      routePrefix: null, 
      model: builder.GetEdmModel()); 
    } 
} 

Теперь вызова службы следующим образом:

GET http://host/customers?$expand=Contacts($filter=IsActive eq true) 

Вы должны получить полезную нагрузку, подобную этой:

{ 
    "@odata.context": "http:/host/$metadata#customers", 
    "value": [ 
    { 
     "Id": 2, 
     "IsActive": true, 
     "Contacts": [ 
     { 
      "Id": 101, 
      "IsActive": true 
     }, 
     { 
      "Id": 103, 
      "IsActive": true 
     } 
     ] 
    } 
    ] 
} 
+0

Однако это очень полный ответ, я уже думал об этом, но я хотел бы выполнить действие на стороне сервера, а не на стороне клиента $ filter.См. Мое последнее предложение в вопросе: «Я знаю, что также могу выполнять фильтрацию на стороне клиента (с помощью фильтра $). Но я хотел бы установить это правильно в части WebApi, поэтому у клиента нет заботиться о фильтрации неактивных результатов ». –

+1

Фильтрация _is_ происходит на стороне сервера. Клиент просто запрашивает определенную фильтрацию с помощью выражения '$ expand = Контакты ($ filter = IsActive eq true). Вы говорите, что не хотите добавлять это выражение в URI запроса? – lencharest

+0

Вы правы, что фильтрация выполняется на сервере да. Возможно, я сформулировал это недостаточно ясно, я хотел бы, чтобы клиентская сторона (которая генерирует URI) не знала о поле IsActive. Решение DynamicFilters - лучший вариант для меня. –

Смежные вопросы