4

我遇到了 Entity Framework 5 和 Oracle DB 的性能问题。

我有一个简单的 SQL 选择:SELECT * FROM NOTE WHERE NOTENUMBER = '1A23456'

NOTENUMBER包含在名为 NOTE 的表的索引中,但该字段不是主键/唯一的。

  • 当我使用 Oracle SQL Developer 执行该语句时,结果会快速返回,并且查询计划显示正在使用 RANGE SCAN。

  • 当我使用实体框架时,生成的 SQL 需要更长的时间(5 秒对 30 毫秒)。

  • 当我使用 Entity Framework 并使用主键字段(NOTE_KEY) 进行查询时,结果返回的速度与使用 SQL Developer 一样快。

我怀疑两件事:

  • EF 和 Oracle.DataAccess-provider 没有使用可用的非唯一索引存在一些问题。如果我有 Entity Framework 5 的调试符号会有所帮助,但我在任何地方都找不到它们。

  • 性能问题存在于 EF 中,关于闭包和/或我在 EF 中使用通用存储库模式的方式:

    如果我这样调用我的存储库:
    var notenumber = "1A23456";
    var notes = repository.All(n => n.NOTENUMBER == notenumber).ToList();
    谓词以如下方式出现在方法All中:
    {n => (n.NOTE == value(Tester.Program+<>c__DisplayClass0).notenumber)}
    并且 EfProf-profiler 将生成的 SQL 跟踪为:

    SELECT "Extent1"."NOTE_KEY" AS "NOTE_KEY",
    "Extent1"."NOTENUMBER" AS "NOTENUMBER",
    "Extent1"."NOTETEXT" AS "NOTETEXT",
    FROM "NOTE_DBA"."NOTE" "Extent1"
    WHERE ("Extent1"."NOTENUMBER" = '1PSA0500237500' /* @p__linq__0 */)

    查询需要~5500ms


    另一方面,如果我这样调用我的存储库:
    var notes = repository.All(n => n.NOTENUMBER == "1A23456").ToList();
    那么谓词如下:
    {n => (n.NOTENUMBER == "1A23456")}
    并且 EfProf-profiler 将生成的 SQL 跟踪为:

    SELECT "Extent1"."NOTE_KEY" AS "NOTE_KEY",
    "Extent1"."NOTENUMBER" AS "NOTENUMBER",
    "Extent1"."NOTETEXT" AS "NOTETEXT",
    FROM "NOTE_DBA"."NOTE" "Extent1"
    WHERE ('1PSA0500237500' = "Extent1"."NOTENUMBER")

    查询需要~30ms

    所以唯一的区别是 WHERE 子句中条件的顺序,而在后者中似乎没有参数被 EF 替换


我使用 VS2010 和 .NET4,并参考 EF5 (v4.4.0.0)。存储库的 All-method 是:

public IQueryable<NOTE> All(Expression<Func<NOTE, bool>> predicate = null)
{
    var setOfNotes = GetDbSet<NOTE>();
    var notesQuery = from note in setOfNotes select note;
    if (predicate != null)
    {
        notesQuery = notesQuery.Where(predicate);
    }
    return notesQuery;
}

我尝试创建一个 CompiledQuery,尝试使用setOfNotes.AsNoTracking()并尝试以 .NET 4.5 为目标 - 性能没有差异。

我能够快速获得此特定查询的一种方法是使用 Oracle 的基本 Data Provider for .NET (ODB.NET) 并手动构建查询,但我宁愿不坚持使用该解决方案。同样,如果我在 where 子句中使用主字段,即使使用 EF 和相同的 All-method,查询也会很快。

所以问题似乎出在EF的某个地方。如果我只有 EntityFramework.dll 的符号,我觉得可以找到更多信息。

EF 调用谓词的方式会不会有问题?'@p_ linq _0'参数如何在 EF 中被替换?

4

3 回答 3

5

我有一个类似的问题。在我的案例中未使用索引的原因是我从.NET 传递了一个字符串(unicode)作为参数。这与非 unicode 数据库字段进行了比较。

解决方案是在将字符串参数传递到 where 子句之前将其转换为非 unicode:

using System.Data.Objects;

EntityFunctions.AsNonUnicode( myUnicodeParam)
于 2012-10-12T09:28:58.373 回答
0

我在代码中看到了一些错误和设计问题。

首先,自定义“All”方法。将所有笔记选择到 notesQuery 中的代码行根本不执行任何操作。

var notesQuery = from note in setOfNotes select note;

请记住,'setOfNotes' 已经是 Note 类型的 IQueryable。该声明实际上是说“从选择所有笔记中选择所有笔记”。LINQ 将在内部完全删除此语句(具有轻微的性能成本),因此可以安全地从您的代码中删除它。

尝试将功能更改为:

public IQueryable<NOTE> All(Expression<Func<NOTE, bool>> predicate = null)
{
    var setOfNotes = GetDbSet<NOTE>();
    return (predicate == null) ? setOfNotes : setOfNotes.Where(predicate);
}

设计中存在更大的基本问题。对于 .NET 集合,方法“All”用于确定集合中每个元素的谓词是否为真。这并不意味着“退回所有物品”。当然,您正在创建自己的存储库,但命名与标准 .NET 使用冲突。我实际上建议您完全摆脱自定义存储库。

LINQ 是“语言集成查询”——重点在于将其与您的代码集成。在它周围再包裹一层是没有意义的,而且违反直觉。DbSet 实现了 IQueryable 接口 - 直接公开它并在您的代码中查询它。它允许 LINQ 更有效地缓存查询并避免冗余。IQueryable 已经存储库,没有必要再创建一个。您刚刚抛弃了 LINQ 的强大功能,回到了存储过程的时代。

如果您在多个位置使用复杂查询,只需在 IQueryable 上编写一个扩展方法,该方法返回另一个 IQueryable。然后就可以GetDbSet().SliceAndDice()了!

回到你原来的问题,你可能把分配和比较混在一起了。您粘贴的代码包括:

var notes = repository.All(n => n.NOTENUMBER = notenumber).ToList();

这是不正确的,它试图将“notenumber”分配给 n.NOTENUMBER 属性,而不是测试相等性。也许这只是堆栈溢出帖子中的一个错字。相反,请尝试:

var notes = repository.All(n => n.NOTENUMBER == notenumber).ToList();

甚至更好:

var notes = GetDbSet<NOTE>().Where(n => n.NOTENUMBER == notenumber).ToList();

我怀疑相等错误只是一个错字,但问题可能在于您在 IQueryable 存储库上“分层”自定义存储库并在它们之间传递表达式的方式,并且正在破坏 EF 正确缓存的能力。

于 2012-09-09T04:08:10.713 回答
0

要找出 SQL 实体框架使用 ToTraceString 方法生成的内容,请参阅:http: //msdn.microsoft.com/en-us/library/system.data.objects.objectquery.totracestring.aspx

可能是 NOTE 表有关系,开启了 eager loading。在这种情况下,EF 生成的 SQL 将尝试加载所有相关数据。

于 2012-08-29T17:18:57.473 回答