实体框架 DbSet<TEntity>.Where(e => true) 性能

3

我正在使用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将会是一个外键。

更新:由于很多评论都聚焦在我之前提到的错误用例上,我会解释一下为什么我提出这个问题。

我有一个类似以下的存储库方法。

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,在这种情况下,我不想对其进行过滤。但我也不想为所有参数的null和非null组合创建不同的谓词。
我知道我可以读取整个集合,然后一个接一个地筛选,但这将在大数据集时使用大量内存。 因此,只是为了澄清我的问题: 这是正确的方法吗?如果所有3个参数都是null(在这种情况下,将返回整个集合),这种方法会导致任何性能问题吗?

如果你知道fkey是空的,为什么还要使用它呢?只需不将其添加到表达式中即可。至于性能,这取决于SQL查询和生成的执行计划。前两个查询可能会创建相同的SQL或等效的执行计划。第三个查询则不会,它将创建一个通用查询,通常会导致糟糕的执行计划。 - Panagiotis Kanavos
看起来你正在尝试在像Entity Framework这样的高级ORM之上实现通用存储库反模式。请检查DbSet<T>。你自己的类提供了什么,而DbSet没有提供的呢?如果你的类只是一个薄薄的包装器,那么为什么要使用它呢?实体没有外键,它们有关系和导航属性。如果你的“存储库”不理解这一点,如果它不能利用关系,因为它是“通用的”,那么它会毫无实际好处地造成伤害。它将抽象级别降低到EF或任何其他ORM所提供的级别以下。 - Panagiotis Kanavos
谢谢回答!我看到我的错误示例引起了一些混淆,请查看我的更新! - Dániel Tarsoly
没有混淆。全捕获查询只是不好的。是的,空值会导致性能问题,因为它们可能会导致缓存和重用错误的执行计划。Jesse表明,在使用LINQ时,您根本不需要它们。这篇文章解释了为什么全捕获查询是不好的。 - Panagiotis Kanavos
1个回答

4

我不确定性能如何,但是你可以在执行之前构建查询来避免这个问题。我认为这提高了代码可读性并减少了混乱。

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;
    });

3
它还可以改善性能。将fKey != null ? e.fKey == fKey : true翻译成SQL语句时,生成的查询语句的(缓存)执行计划会依赖于第一次执行时fkey的值。这有50%的机率会生成错误的执行计划,例如扫描表而不是使用索引搜索。 - Panagiotis Kanavos
1
我预计使用简单的 var 无法奏效,因为 context.Set<>.Where() 具有不同的结果类型。可能 IQueryable<TEntity> query = ... 可以奏效。 - Peter B
我完全同意这对我的例子来说将会是更好的解决方案,但我的第一个问题仍然存在。我在问题更新中进一步解释了为什么我要使用它。 - Dániel Tarsoly
@DánielTarsoly 我想 Panagiotis Kanavas 的评论回答了那个问题。 - Jesse de Wit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接