【问题标题】:Entity Framework DbSet<TEntity>.Where(e => true) performance实体框架 DbSet<TEntity>.Where(e => true) 性能
【发布时间】:2023-04-05 01:52:02
【问题描述】:

我正在使用 Entity Framework 6 访问数据库。

以下两种方法有性能差异吗?

public IEnumerable<TEntity> GetAll()
{
    using (var context = new DbContext())
        return context.Set<TEntity>().ToList();
}

public IEnumerable<TEntity> GetAll()
{
    using (var context = new DbContext())
        return context.Set<TEntity>().Where(e => true).ToList();
}

我问是因为我想使用条件谓词。如下所示。

public IEnumerable<TEntity> GetAll(TKey fKey)
{
    using (var context = new DbContext())
        return context.Set<TEntity>()
                      .Where(e => fKey != null ? e.fKey == fKey : true).ToList();
}

fKey 是外键。

更新:由于许多 cmets 都关注我的最后一个示例,我同意这是一个不好的用例,我将解释我为什么要问。

所以我有如下所示的存储库方法。

public class EntityRepository
{
    public IEnumerable<Entity> GetAll(Expression<Func<Entity, bool>> predicate)
    {
        using (var context = new DbContext())
            return context.Set<Entity>.Where(predicate).ToList();
    }
}

我正在使用这种方法形成 Web API 控制器的操作方法。类似于以下内容。

public IHttpActionResult GetEntities(string param1, string param2, string param3)
{
    Expression<Func<Entity, bool>> predicate = e =>
         (param1 != null ? e.field1 == param1 : true)
         && (param2 != null ? e.field2 == param2 : true)
         && (param3 != null ? e.field3 == param3 : true);
    var entities = EntityRepository.GetAll(predicate);
    return Ok(entities);
}

所以在这里我从 URI 中获取一些查询参数,并根据它们创建谓词。其中一些参数可以是null,在这种情况下我不想过滤这些参数。但我不想为是否为nulls 的所有参数组合创建不同的谓词。

我知道我可以读取整个数据集并在之后逐个过滤,但这会占用大量内存来处理大数据集。

所以只是为了澄清我的问题:这是正确的方法吗?如果所有 3 个参数都是 null(在这种情况下将返回整个集合),此方法是否会导致任何性能挫折?

【问题讨论】:

  • 如果您知道 fkey 为空,为什么要使用它?只是不要将它添加到表达式中。至于性能,取决于SQL查询和生成的执行计划。前两个查询可能会创建相同的 SQL 或等效的执行计划。第三个不会,它会创建一个包罗万象的查询,这通常会导致错误的执行计划
  • 看起来您正在尝试在更高级别的 ORM(如实体框架)之上实现通用存储库 anti 模式。检查DbSet&lt;T&gt;。您自己的课程提供了 DbSet 没有的什么?如果你的类只是一个薄包装器,为什么你要使用它? 实体没有外键,它们有关系和导航属性。如果您的“存储库”不理解这一点,如果它因为“通用”而无法利用关系,那么它会造成伤害而没有真正的好处。它降低抽象级别低于 EF 或任何其他 ORM 提供的抽象级别
  • 感谢您的回答!我可以看到我的错误示​​例引起了一些混乱,所以请检查我的更新!
  • 没有混淆。包罗万象的查询很糟糕。是的,空值会导致性能问题,因为它们会导致错误的执行计划被缓存和重用。 Jesse 表明,在使用 LINQ 时,您一开始就不需要它们。 This article explains 为什么包罗万象的查询不好

标签: c# database performance entity-framework entity-framework-6


【解决方案1】:

我不了解性能,但您可以通过在执行前构建查询来解决所有问题。我认为这提高了代码的可读性并减少了混乱。

public IEnumerable<TEntity> GetAll(TKey fKey)
{
    using (var context = new DbContext())
    {
        IQueryable<TEntity> query = context.Set<TEntity>();

        if (fKey != null)
        {
            query = query.Where(e => e.fKey == fKey);
        }

        return query.ToList();
    }
}

编辑: 调用您的问题编辑,我认为以下方法将具有相同的用法,但避免了 sql 查询中不必要的语句:

public IEnumerable<Entity> GetAll(
    Func<IQueryable<Entity>, IQueryable<Entity>> query)
{
    using (var context = new DbContext())
        return query(context.Set<Entity>).ToList();
}

// then use like this:
EntityRepository.GetAll((entities) =>
    {
        var query = entities;
        if (param1 != null) query = query.Where(e => e.field1 == param1);
        if (param2 != null) query = query.Where(e => e.field2 == param2);
        if (param3 != null) query = query.Where(e => e.field3 == param3);
        return query;
    });

【讨论】:

  • 它还可以提高性能。 fKey != null ? e.fKey == fKey : true 将被转换为 SQL,生成一个查询,其(缓存的)执行计划将取决于第一次执行时 fkey 的值。生成 错误 执行计划的可能性为 50%,例如扫描表而不是使用索引搜索
  • 我希望这不适用于简单的var,因为context.Set&lt;&gt;.Where() 具有不同的结果类型。可能IQueryable&lt;TEntity&gt; query = ... 会起作用。
  • 我完全同意这对于我的示例来说是一个更好的解决方案,但我的第一个问题仍然存在。我进一步解释了为什么我会在我的问题更新中使用它。
  • @DánielTarsoly 我猜 Panagiotis Kanavas 的 cmets 回答了这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 1970-01-01
  • 2010-12-23
  • 2013-04-10
  • 2014-04-22
  • 1970-01-01
相关资源
最近更新 更多