【问题标题】:Ensure IQueryable<T>.Where() runs a SQL query rather than filtering in memory确保 IQueryable<T>.Where() 运行 SQL 查询而不是在内存中过滤
【发布时间】:2019-09-27 05:30:22
【问题描述】:

这段代码将我的整个表格下载到内存中并在那里过滤。

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)

已编辑以包含 id 类和 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&lt;T&gt; 转换为 linq-to-sql 中的 int

【问题讨论】:

  • 我碰巧方便地在DbContext 中进行的快速测试,在您的两种模式之间生成的查询没有明显差异。对我来说,它们都在数据库上完全运行。您能否展示您如何在Id&lt;T&gt; 中实现int 演员表?这可能会对事情的执行方式产生一些影响。
  • 这是 EF6 还是 EF Core?
  • 我的测试是在 EF6 中完成的。正如 David 的评论所暗示的那样,Core 也可能会改变一些事情。
  • @BradleyUffner 实际上,我怀疑 EF6 实际上会抛出错误,而不是拖入 RAM 中执行类似的代码,而 EFCore 会秘密地在内存中执行操作,除非您告诉它不要这样做。
  • 我完全预计会收到有关不支持表达式的错误。我很惊讶得到我所做的结果。

标签: c# performance linq entity-framework-core


【解决方案1】:

在 Entity Framework Core 中,如果它无法评估子句,它将悄悄地评估内存中的所有内容。请注意,我悄悄地说是因为您应该在日志中收到一条消息,告诉您它已经发生了。

您可以通过配置上下文来更改此行为,例如:

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

更多信息请参见this doc

您也可以在启动代码中执行此操作,例如:

services.AddDbContext<ApplicationDbContext>(options =>
{
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    options.ConfigureWarnings(warnings => 
        warnings.Throw(RelationalEventId.QueryClientEvaluationWarning);
});

【讨论】:

  • awww 愚蠢,愚蠢的 EF。我希望他们继续 L2S。等等,不,我没有。它本身就是完美的。
  • 哦,比我的回答快 1 分钟。让我把这个放在这里,因为我删除了我的。应用它会破坏您已经编码的所有客户端评估代码。因此,您首先需要在应用每个查询时对其进行测试
  • 谢谢大卫!我不敢相信我错过了 ms 文档。
  • 密切关注/为即将推出的 EF Core 3 - LINQ queries are no longer evaluated on the client 做好准备 :)
  • @IvanStoev Hallelujah!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 2010-10-30
  • 1970-01-01
  • 2020-04-11
  • 2021-11-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多