Передача запроса GetWhere (Func<entityDTO, bool>) в метод уровня данных, для работы которого требуется параметр (Func<entity, bool>)

У меня есть следующий метод в классе доступа к данным, который использует структуру объекта:

public static IEnumerable<entityType> GetWhere(Func<entityType, bool> wherePredicate)
{
    using (DataEntities db = new DataEntities())
    {
        var query = (wherePredicate != null)
            ? db.Set<entityType>().Where(wherePredicate).ToList()
            : db.Set<entityType>().ToList();                    
        return query;
    }
}

Это прекрасно работает, когда я использую сущности во всех слоях... однако я пытаюсь перейти к использованию класса DTO, и я хотел бы сделать что-то вроде следующего:

public static IEnumerable<EntityTypeDTO> GetWhere(Func<EntityTypeDTO, bool> wherePredicate)
{
    //call a method here which will convert Func<EntityTypeDTO,bool> to 
    // Func<EntityType,bool>

    using (DataEntities db = new DataEntities())
    {
        var query = new List<EntityType>();
        if (wherePredicate == null)
        {
            query = db.Set<EntityType>().ToList();
        }
        else
        {   
            query = (wherePredicate != null)
                ? db.Set<EntityType>().Where(wherePredicate).AsQueryable<EntityType>().ToList()
                : db.Set<EntityType>().ToList();
        }
        List<EntityTypeDTO> result = new List<EntityTypeDTO>();
        foreach(EntityType item in query)
        {
            result.Add(item.ToDTO());
        }

        return result;
    }
}

По сути, я хочу метод, который будет конвертировать Func в Func.

Я думаю, что я должен разбить Func в дерево выражений, а затем перестроить его как-то в entityType?

Я хочу сделать это, чтобы слой представления мог просто передавать запросы Expression?

Я что-то упускаю из виду или есть более простой шаблон проектирования, который может передавать запрос из DTO в класс доступа к данным, не зная деталей запроса?

Я пытался заставить DTO наследовать сущность, которая, похоже, тоже не работает?

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

2 ответа

Решение

Во-первых, я бы посоветовал вам поместить собственный запрашивающий слой перед Entity Framework, а не разрешать передачу любого произвольного Func, потому что в будущем будет очень легко передать Func, который Entity Framework не может перевести в SQL оператор (он может переводить только некоторые выражения - основы хороши, но если ваше выражение вызывает, например, метод C#, то Entity Framework, вероятно, потерпит неудачу).

Таким образом, в вашем поисковом слое могут быть классы, которые вы создаете в качестве критериев (например, класс поиска "ContainsName" или класс "ProductHasId"), которые затем преобразуются в выражения в вашем слое поиска. Это полностью отделяет ваше приложение от ORM, что означает, что детали ORM (например, сущности или ограничения, которые могут и не могут быть переведены Funcs) не просачиваются. Там было много написано об этом, некоторые договоренности.

И последнее замечание: если вы работаете близко к слою ORM, Entity Framework очень умен, и вы, вероятно, могли бы пройти долгий путь, не пытаясь перевести ваш Func в Func . Например, в приведенном ниже коде доступ к "context.Products" возвращает "DbSet", а вызов Select для него возвращает IQueryable, а вызов Where для этого также возвращает IQueryable. Entity Framework преобразует все это в один оператор SQL, поэтому он не вытягивает все другие продукты в память, а затем фильтрует идентификатор этого набора памяти, фактически выполняет фильтрацию в SQL, даже если фильтр работает с проецируемым тип (который эквивалентен DTO в вашем случае), а не сущность Entity Framework -

var results = context.Products
    .Select(p => new { ID = p.ProductID, Name = p.ProductName })
    .Where(p => p.ID < 10)
    .ToList();

Выполненный SQL:

SELECT 
    [Extent1].[ProductID] AS [ProductID], 
    [Extent1].[ProductName] AS [ProductName]
FROM [dbo].[Products] AS [Extent1]
WHERE [Extent1].[ProductID] < 10

Итак, если вы изменили свой код, чтобы получить что-то вроде..

return context.Products
    .Map<Product, ProductDTO()>()
    .Where(productDtoWherePredicate)
    .ToList();

... тогда вы можете быть в порядке с Funcs, которые у вас уже есть. Я предполагаю, что у вас уже есть какие-то функции отображения, которые можно получить от EF-сущностей до DTO (но если нет, то вы можете обратиться к AutoMapper, чтобы помочь вам - у которого есть поддержка "проекций", которые в основном являются картами IQueryable).

Я собираюсь изложить это как ответ. Спасибо Дэну за быстрый ответ. Глядя на то, что вы говорите, я могу написать набор классов запросов / фильтров. например, возьмите следующий код:

GetProducts().GetProductsInCategory().GetProductsWithinPriceRange(minPrice, maxPrice);

Этот код будет работать так: Get Products будет получать все продукты в таблице, а остальные функции будут фильтровать результаты. если все запросы выполняются таким образом, это может создать значительную нагрузку на соединения уровня доступа к данным / соединения с сервером БД... не уверен.

или же

Альтернативный вариант, над которым я буду работать: если каждая функция создает выражение Linq, я могу объединить их следующим образом: как объединить несколько запросов linq в один набор результатов? это может позволить мне сделать это таким образом, чтобы я мог вернуть отфильтрованный набор результатов из базы данных.

В любом случае я отмечаю это как ответ. Я обновлю, когда у меня будет больше деталей.

Другие вопросы по тегам