Фильтрация результатов Entity Framework на SQL Server - PullRequest
2 голосов
/ 20 января 2012

Сначала: мы не делаем TPH (таблица на иерархию), что значительно упростит эту задачу.

У меня около 20 POCO, которые в некоторых случаях имеют схожие свойства.,Мне нравятся похожие свойства ___.CreatedDate и ___.UpdatedDate.Для некоторых POCO UpdatedDate не имеет смысла, поэтому у них нет этого свойства.CreatedDate будет существовать всегда, UpdatedDate может быть нулевым.Иногда есть и третье поле.

Запрос на получение объектов из базы данных всегда выглядит одинаково, независимо от того, какой из 20 POCO я запрашиваю.Однако запрос дублировался 20 раз, по одному для каждого типа объекта.Мне удалось выделить часть извлечения для метода расширения (от IDbSet<T>), чтобы выполнить первоначальный поиск и объединения, оставив фильтрацию диапазона дат в качестве упражнения для потребителя.Теперь я хочу объединить 20 фильтров с практически одинаковым диапазоном дат в один, но у меня возникают проблемы с пониманием запросов.

Логика фильтрации дат, согласно POCO, заключается в том, что UpdatedDate необходимо проверить и сравнить его значениедо порога.Если UpdatedDate был нулевым (или свойство не существовало), то вместо этого следует использовать CreatedDate.

Я начал с создания метода получения статических свойств для каждого POCO, который яназванный "DateFields".Он набирается как IEnumerable<Expression<Func<T, DateTime?>>>, который выглядит следующим образом:

get
{
    yield return x => x.UpdatedDate;
    yield return x => x.CreatedDate;
    yield return x => x.Date;
}

Эти поля возвращаются в том порядке, в котором я хочу их проверять, и являются уникальными для каждого POCO.

Затем я создалпредикат для проверки каждого значения в верхнем и нижнем диапазонах:

public static bool DatesBetween<T> (T value, IEnumerable<Expression<Func<T, DateTime?>>> dates, DateTime? dateFrom, DateTime? dateTo)
{
    var firstDate = dates
        .Select(expression => expression.Compile())
        .FirstOrDefault(func => func(value) != null);

    if (firstDate == null)
        return false;

    var minDate = dateFrom.GetValueOrDefault(SqlDateTime.MinValue.Value);
    var maxDate = dateTo.GetValueOrDefault(SqlDateTime.MaxValue.Value);

    var actualDate = firstDate(value);

    return (minDate <= actualDate && actualDate <= maxDate);
}

Однако, как и ожидалось, когда я пытаюсь использовать это в своем IQueryable, я получаю исключение времени выполнения, потому что нет преобразования в DatesBetweenв SQL.Я надеялся, что, сохранив дерево выражений, я смогу ... я не знаю ... держать EF счастливым.Все это используется следующим образом:

return Entities
    .GetEntitiesBySomeField(field := "magic value")
    .Where(entity => RepositoryExtensions.DatesBetween(entity, MyPOCO.MyDates, dateFrom, dateTo));

Это то, что я спрашиваю, ясно, и есть ли способ выполнить этот тип общей фильтрации без выполнения AsEnumerable () (который работает, но нене выполнить мою задачу фильтрации по БД).

Ответы [ 2 ]

1 голос
/ 20 января 2012

Я недавно сделал что-то похожее с этим в проекте. В итоге я реализовал шаблон репозитория и создал в репозитории метод под названием «ApplyFilter», который выглядел примерно так:

void ApplyFilter(Expression<Func<TEntity, bool>> predicate)
{
    if(this.resultSet == null)
    {
        this.resultSet = this.context.Set<TEntity>().AsQueryable();
    }

    this.resultSet = this.resultSet.Where(predicate);
}

И в моем репозитории было свойство ResultSet, которое просто возвращало IQueryable поле resultSet и вызывало перечисление результатов (вызывая вызов базы данных EF). Таким образом, я мог бы применить все динамически созданные выражения в чистом почему, прежде чем перечислять результаты.

Реальная разница между тем, что делает мой код, и тем, что делает ваш, состоит в том, что мой код добавляет логический предикат в конечное дерево выражений, когда вы пытаетесь использовать свой в конечном дереве выражений. Мой способ в основном создает / добавляет предложение WHERE к SQL, а ваш пытается генерировать вызов функции SQL из метода "DatesBetween".

Вот что попробовать:

    public static Expression<Func<TEntity, bool>> MakeDateRange<TEntity>(DateTime? dateFrom, DateTime? dateTo)
    {
        var et = typeof(TEntity);
        var param = Expression.Parameter(et, "a");
        var prop = et.GetProperty("UpdatedDate");
        Expression body = null, left = null, right = null;
        if (prop == null)
        {
            prop = et.GetProperty("CreatedDate");
            if (prop == null)
            {
                prop = et.GetProperty("Date");
            }
        }

        if (dateFrom.HasValue)
        {
            left = Expression.GreaterThanOrEqual(Expression.PropertyOrField(param, prop.Name), Expression.Constant(dateFrom.GetValueOrDefault()));
        }

        if (dateTo.HasValue)
        {
            right = Expression.LessThanOrEqual(Expression.PropertyOrField(param, prop.Name), Expression.Constant(dateTo.GetValueOrDefault()));
        }

        if (left != null && right != null)
        {
            body = Expression.AndAlso(left, right);
        }
        else if (left != null)
        {
            body = left;
        }
        else
        {
            body = right;
        }

        return Expression.Lambda<Func<TEntity, bool>>(body, param);
    }
0 голосов
/ 20 января 2012

Так что, если я понимаю, вы хотите иметь возможность выполнять один и тот же запрос для нескольких таблиц, имеющих похожую структуру?

Если это так, рассмотрите возможность создания интерфейса, который определяет эти свойства и имеетPOCO реализует этот интерфейс.Тогда у вашего общего параметра есть ограничение этого интерфейса.Оттуда вы сможете написать общий запрос, который использует свойства интерфейса.

...