Как заставить EF. net фильтровать все данные на SQL сервере, а не на C# - PullRequest
1 голос
/ 20 февраля 2020

Я делал несколько тестов, используя Entity Framework. Net core. Я включил SQL профиль сервера и начал сравнивать свой запрос и то, что EF выполняет на SQL Сервере.

Вот мой модальный класс:

public class Offer : BaseModel
    {
        public DateTime HostStartDate { get; set; }
        public DateTime HostEndDate { get; set; }
        public int Guests { get; set; }
        public decimal AmountOffer { get; set; }
        public OfferStatus Status { get; set; }
        public PaymentStatus PaymentStatus { get; set; }

        public long CityId { get; set; }
        public virtual City City { get; set; }

        [Required]
        public string UserId { get; set; }
        public virtual ApplicationUser User { get; set; }

        public virtual ICollection<Bid> Bids { get; set; }
        public bool IsDeleted { get; set; }

        [NotMapped]
        public bool IsVisible => HostStartDate.Date >= DateTime.Today.Date;



        public void SetSoftDelete()
        {
            IsDeleted = true;
            Bids.ToList().ForEach(n => n.SetSoftDelete());
        }
    }

 public class Bid : BaseModel , IStateAware
    {
        [NotMapped]
        public override long Id { get; set; }

        public long OfferId { get; set; }
        public virtual Offer Offer { get; set; }

        public long PropertyId { get; set; }
        public virtual Property Property { get; set; }

        public DateTime? Accepted { get; set; }
        public bool Read { get; set; }
        public bool IsDeleted { get; set; }

        [NotMapped]
        public ModelState State { get; set; }

        [NotMapped]
        public bool IsPayed => Accepted.HasValue;

        [NotMapped]
        public bool IsAccepted => Accepted.IsNotNull();

        public void SetSoftDelete()
        {
            IsDeleted = true;
            State = ModelState.Modified;
        }
    }

У меня есть generi c класс для выполнения поиска, например:

    public IQueryable<TEntity> FindBy(
        Expression<Func<TEntity, bool>> filter = null,
        Expression<Func<TEntity, object>> includeProperty = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null)
    {
        IQueryable<TEntity> query = context.Set<TEntity>();

        if (filter.IsNotNull())
        {
            query = query.Where(filter);
        }

        if (includeProperty.IsNotNull()) 
        {
            query = query.Include(includeProperty);
        }

        if (orderBy.IsNotNull())
        {
            query = orderBy(query);
        }

        return query.AsNoTracking();
    }

Я называю это так:

var finalEnd = new DateTime(end.Year, end.Month, end.Day, 23, 59, 59);
var data = await _bidRepo.FindBy(n => !n.OfferId.Equals(offerId)
                                            && n.PropertyId.Equals(propertyId)
                                            && !n.IsDeleted
                                            && !n.IsAccepted
                                            && !n.IsPayed
                                            && !n.IsInvalidate
                                            && ((n.Offer.HostStartDate.Date >= start.Date && n.Offer.HostStartDate.Date <= end.Date) ||
                                            (n.Offer.HostEndDate > finalEnd && n.Offer.HostEndDate.Date <= end.Date))
                                            ).ToListAsync();

Мой запрос возвращает 4 элемента, и это нормально. Но на SQL профиле сервера мой запрос не заботится ни о чем, кроме даты.

Это профиль:

exec sp_executesql N'SELECT [n].[OfferId], [n].[PropertyId], [n].[Accepted], [n].[CreatedBy], [n].[CreatedDate], [n].[IsDeleted], [n].[IsInvalidate], [n].[ModifiedBy], [n].[ModifiedDate], [n].[Read]
FROM [Bids] AS [n]
INNER JOIN [Offers] AS [n.Offer] ON [n].[OfferId] = [n.Offer].[Id]
WHERE ((CONVERT(date, [n.Offer].[HostStartDate]) >= @__start_Date_2) AND (CONVERT(date, [n.Offer].[HostStartDate]) <= @__end_Date_3)) OR (([n.Offer].[HostEndDate] > @__finalEnd_4) AND (CONVERT(date, [n.Offer].[HostEndDate]) <= @__end_Date_3))',N'@__start_Date_2 datetime2(7),@__end_Date_3 datetime2(7),@__finalEnd_4 datetime2(7)',@__start_Date_2='2020-02-23 00:00:00',@__end_Date_3='2020-02-25 00:00:00',@__finalEnd_4='2020-02-25 23:59:59'

В этом SQL заявлении сервера, Я не вижу ни одного из логических фильтров или идентификаторов.

Как я могу убедиться, что все предложения выполняются на сервере SQL и не переносят часть даты в C#, а затем удаляют ее?

1 Ответ

1 голос
/ 21 февраля 2020

Здесь есть некоторые особенности реализации EF Core, но основная проблема заключается в том, что вы используете не сопоставленные / получить только свойства, которые нельзя преобразовать в SQL. EF Core 3.0+ просто выдаст исключение времени выполнения, но EF Core 1.x / 2.x оценит такие условия в памяти после извлечения данных из базы данных.

Почему они не могут быть переведены? Поскольку EF Core не является компилятором, и все, что он видит, это что-то вроде

public bool IsPayed { get; }

Для того, чтобы быть переводимым, EF Core должен «увидеть» реализацию внутри дерева выражений запросов. Это означает, что в принципе вы не можете использовать такие свойства (а также пользовательские методы, такие как IsNotNull) в запросе LINQ to Entities и должны кодировать их напрямую, например. вместо

&& !n.IsPayed

вы должны использовать

&& !n.Accepted.HasValue

Аналогично для && !n.IsAccepted (и, скорее всего, !n.IsInvalidate).

Конечно, это дублирование кода и менее читабелен, но это общая проблема с IQueryable деревьями выражений, которые не имеют компилятора или решения BCL. Есть некоторые сторонние библиотеки, которые пытаются решить эту проблему. Например, Лямбда-впрыск от NeinLinq . Если вы настаиваете на использовании OOP в своей модели данных / запросах, рассмотрите возможность использования некоторых из этих сторонних библиотек, поскольку вы не получите его из C# / BCL / EF Core.

...