Убедитесь, что IQueriable <T>.Where () выполняет SQL-запрос, а не фильтрует в памяти - PullRequest
4 голосов
/ 09 мая 2019

Этот код загружает всю мою таблицу в память и фильтрует там.

class Book {
    Id<Book> BookId;
    Id<Author> AuthorId;
}

class Person {
    Id<Person> PersonId;
}

class BookLookup {
   List<Book> GetBooksForPerson(DbContext db, Id<Person> personId) {

    // yes authorId maps 1-to-1 to personId, legacy
    // codebase, can't change that right now
    return db.Books
      .Where(b => (int)b.AuthorId == (int)personId)
      .ToList();
   }
}

Этот код работает на SQL и загружает только отфильтрованные строки.

class BookLookup {
   List<Book> GetBooksForPerson(DbContext db, Id<Person> personId) {

    // yes authorId maps 1-to-1 to personId, legacy
    // codebase, can't change that right now
    var authorId = (Id<Author>)(int)personId;
    return db.Books
      .Where(b => b.AuthorId == authorId)
      .ToList();
   }
}

Оба решения компилируются и работают, но одно из них вызывает ОГРОМНУЮ проблему с производительностью.

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

Я бы хотел что-то вроде этого, которое выдает или не может скомпилировать, когда это не может быть преобразовано в sql.

db.Books.WhereDb(b => (int)b.AuthorId == (int)personId)
db.Books.Where(b => (int)b.AuthorId == (int)personId, 
                FilterOption.MustRunInDb)


* отредактировано для включения идентификатора класса и версии ef:
public struct Id<T> : IEquatable<Id<T>>, IComparable
{
    private readonly int _value;

    private Id(int value)
    {
        _value = value;
    }

    public static explicit operator int(Id<T> instance)
    {
        return instance._value;
    }

    public static explicit operator Id<T>(int value)
    {
        return new Id<T>(value);
    }

    public static bool operator ==(Id<T> value1, Id<T> value2)
    {
        return value1.Equals(value2);
    }

    public static bool operator !=(Id<T> value1, Id<T> value2)
    {
        return !(value1 == value2);
    }

    public bool Equals(Id<T> other)
    {
        return _value.Equals(other._value);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is Id<T> type && Equals(type);
    }

    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }

    /// <summary>
    /// Prints the debug string representation of this object
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        var typeName = GetType().GenericTypeArguments.First().Name;
        return $"Id<{typeName}>:{_value}";
    }

    public int CompareTo(Id<T> obj)
    {
        return _value.CompareTo(obj._value);
    }

    public int CompareTo(object obj)
    {
        if (ReferenceEquals(null, obj)) return 1;

        if(obj is Id<T> type)
        {
            return CompareTo(type);
        }

        return 1;
    }
}

Использование EF Core (v2.1.4) и пользовательского помощника для преобразования Id в int в linq-to-sql

1 Ответ

3 голосов
/ 09 мая 2019

В Entity Framework Core, если он не может оценить предложение, он будет вместо этого спокойно оценивать все в памяти.Обратите внимание, что я сказал тихо, потому что вы должны получить в журнале сообщение о том, что это произошло.

Вы можете изменить это поведение, настроив свой контекст, например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer("<connection string>")
        .ConfigureWarnings(warnings => 
            warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}

См. этот документ для получения дополнительной информации.

Вы также можете сделать это в своем коде запуска, например:

services.AddDbContext<ApplicationDbContext>(options =>
{
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    options.ConfigureWarnings(warnings => 
        warnings.Throw(RelationalEventId.QueryClientEvaluationWarning);
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...