Entity Framework Core:Guid 大于分页

Posted

技术标签:

【中文标题】Entity Framework Core:Guid 大于分页【英文标题】:Entity Framework Core: Guid Greater Than for Paging 【发布时间】:2019-02-28 07:08:23 【问题描述】:

SQL Server 在大型表(> 1000000 行)上使用 Skip/Take 变得非常慢。表键列类型是 Guid,我知道最后读取的行。我尝试像这样加载下一页

var keyGuid = Guid.NewGuid(); // Key Guid of the last read row
// var result1 = DbContext.Entity.Where(x => x.Id > keyGuid).Take(10).ToList();
var result2 = DbContext.Entity.Where(x => x.Id.CompareTo(keyGuid) > 0).Take(10).ToList();

虽然第一种方法无法编译,但第二种方法在客户端评估查询 (QueryClientEvaluationWarning),也没有用。

很遗憾,我无法以任何方式修改数据库。

是否有任何没有自定义 SQL 的“本机”EF Core 解决方案?如果可以拦截 SQL 代码生成并手动解析表达式可能没问题(但如何?)

【问题讨论】:

您可以使用 EF Core 自己编写 SQL 查询,如下所示:docs.microsoft.com/en-us/ef/core/querying/raw-sql 或 entityframeworktutorial.net/EntityFramework4.3/… 谢谢,这将是我最后的选择。它是一个通用组件,适用于任何表,包括一些包含复合键的表。没那么容易,因为需要更多的自定义 sql 代码生成。 @Florian 如果没有OrderBy() 子句,您的代码将无法运行。除非指定了 ORDER BY 子句,否则查询结果中没有隐式顺序。即使ID 是聚集索引,连接和并行执行也会改变结果的显示方式。 【参考方案1】:

EF Core 2.x

从 v2.0 开始,EF Core 支持所谓的Database scalar function mapping。它没有很好的文档记录,通常用于映射一些数据库功能。但 fluent API 还允许您通过 HasTranslation 方法提供自定义翻译:

设置将被调用以执行此函数的自定义翻译的回调。回调采用与传递给函数调用的参数相对应的表达式集合。回调应该返回一个表示所需翻译的表达式。

下面的类通过定义几个自定义扩展方法来比较Guid值并为它们注册一个自定义翻译,将方法调用表达式转换为二进制比较表达式,基本上模拟了缺少的>>=<<= Guid 运算符,只要数据库支持它们(SqlServer 支持),就可以将它们转换为 SQL 并正确执行服务器端。

这里是实现:

public static class GuidFunctions

    public static bool IsGreaterThan(this Guid left, Guid right) => left.CompareTo(right) > 0;
    public static bool IsGreaterThanOrEqual(this Guid left, Guid right) => left.CompareTo(right) >= 0;
    public static bool IsLessThan(this Guid left, Guid right) => left.CompareTo(right) < 0;
    public static bool IsLessThanOrEqual(this Guid left, Guid right) => left.CompareTo(right) <= 0;
    public static void Register(ModelBuilder modelBuilder)
    
        RegisterFunction(modelBuilder, nameof(IsGreaterThan), ExpressionType.GreaterThan);
        RegisterFunction(modelBuilder, nameof(IsGreaterThanOrEqual), ExpressionType.GreaterThanOrEqual);
        RegisterFunction(modelBuilder, nameof(IsLessThan), ExpressionType.LessThan);
        RegisterFunction(modelBuilder, nameof(IsLessThanOrEqual), ExpressionType.LessThanOrEqual);
    
    static void RegisterFunction(ModelBuilder modelBuilder, string name, ExpressionType type)
    
        var method = typeof(GuidFunctions).GetMethod(name, new[]  typeof(Guid), typeof(Guid) );
        modelBuilder.HasDbFunction(method).HasTranslation(parameters =>
        
            var left = parameters.ElementAt(0);
            var right = parameters.ElementAt(1);
            return Expression.MakeBinary(type, left, right, false, method);
        );
    

您只需将以下行添加到您的上下文中 OnModelCreating 覆盖:

GuidFunctions.Register(modelBuilder);

然后在您的查询中简单地使用它们:

var result = DbContext.Entity
    .Where(x => x.Id.IsGreaterThan(keyGuid))
    .Take(10).ToList();

EF Core 3.0

HasTranslation 现在接收并返回SqlExpression 实例,所以

return Expression.MakeBinary(type, left, right, false, method);

应该替换为

return new SqlBinaryExpression(type, left, right, typeof(bool), null);

【讨论】:

以上是关于Entity Framework Core:Guid 大于分页的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 快速开始

EF6 System.Data.Entity.Core.EntityKey 在 Entity Framework Core 中的等价物是啥?

张高兴的 Entity Framework Core 即学即用:创建第一个 EF Core 应用

Entity Framework Core 迁移命令

Entity Framework Core - 计算字段排序

Entity Framework Core 性能优化