【问题标题】:Dynamic EF/Linq query comparing Guid/UniqueIdentifier values比较 Guid/UniqueIdentifier 值的动态 EF/Linq 查询
【发布时间】:2019-09-03 21:14:02
【问题描述】:

我需要使用大于语句针对 Guid/UniqueIdentifier 列生成 where 子句。我的问题是我必须动态生成查询。这是我想要的一个例子:

myTable.Where(x => Guid.Empty.CompareTo(x.ADifferentGuidColumn) > 0)

生成此 SQL(我在这里使用 LINQPad 获取 SQL):

-- Region Parameters
DECLARE @p0 UniqueIdentifier = '00000000-0000-0000-0000-000000000000'
-- EndRegion
SELECT <columns...>
FROM [myTable] AS [t0]
WHERE (@p0 > [t0].[ADifferentGuidColumn])

这就是我所拥有的。这是一个更大的通用方法,其中T 参数是表/域模型对象。它可以编译,但是在创建 startPointBodyThe binary operator GreaterThan is not defined for the types 'System.Guid' and 'System.Guid'. 时会爆炸

Guid startPoint = Guid.NewGuid(); //actual value in real code...
IQueryable<T> set = context.Set<T>().AsQueryable();
var parameter = Expression.Parameter(typeof(T), "x");
//find primary key
IProperty primaryKey = context.Model.FindEntityType(typeof(T).FullName).FindPrimaryKey().Properties.FirstOrDefault();
//expression for column we're filtering
var startPointMember = Expression.Property(parameter, primaryKey.Name);
ConstantExpression startPointConstant = Expression.Constant(startPoint, startPoint.GetType());
var startPointBody = Expression.GreaterThan(startPointMember, startPointConstant);
var startPointWhereExpression = Expression.Lambda<Func<T, bool>>(startPointBody, parameter);
query = query.Where(startPointWhereExpression);

您可以在我想要的示例中看到我实际上正在使用Guid.CompareTo(),但我不知道如何以这种动态方式编写它。我希望我可以作弊,因为生成的 SQL 只是做一个简单的&gt; 比较,但 EF 抓住了我。

【问题讨论】:

  • 我正在为您在这里尝试做的事情而苦苦挣扎。你是说你事先不知道你会SELECTing 反对哪张桌子?这就是为什么需要动态查询的原因?
  • 是的,我不知道桌子是什么,因此是动态的东西。它已经运行了几个星期了。我今天需要添加这个 where 子句并遇到了这堵墙,因为 &gt; 不存在/没有为 Guid 类型实现。
  • 我要走出公寓去吃点东西,然后细细咀嚼,但是是否可以只为 Guid 类型实现 GreaterThan?

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


【解决方案1】:

这是一个包含示例数据的有效解决方案。我把它放在连接到 docker 中的本地 sql 服务器实例的 linqpad 中。

这是表格和数据(一些 guid 被硬编码在演示代码中):

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[test](
    [col1] [uniqueidentifier] NOT NULL,
    [col2] [uniqueidentifier] NOT NULL
) ON [PRIMARY]
GO

insert into test (col1,col2) values ('d3aaaf86-d4e5-4d77-897e-1b36c1100448', '09e0073a-be0e-405a-9564-d34056c8e42a')
insert into test (col1,col2) values ('d3aaaf86-d4e5-4d77-897e-1b36c1100448', 'a7a5aee0-87f4-42bb-90ac-dac7b52bffbf')
insert into test (col1,col2) values ('d3aaaf86-d4e5-4d77-897e-1b36c1100448', '104ab3c0-8521-453f-bf48-552358618a90')
insert into test (col1,col2) values ('d3aaaf86-d4e5-4d77-897e-1b36c1100448', '6128657b-5c84-4c95-afbf-4bfe06c2c7ea')
insert into test (col1,col2) values ('d3aaaf86-d4e5-4d77-897e-1b36c1100448', '96bc5a4f-0baf-44fb-a85c-fbddcca82ff9')
insert into test (col1,col2) values ('ac15b74b-593f-4d28-9f80-46cc84a89e79', 'db4621e0-b6fe-40ad-a4c5-601f875c5bbf')
insert into test (col1,col2) values ('a5071253-3a12-45c8-a495-d96bb56f49c9', 'a4536fa3-922a-43b0-a568-abfcf2daba9f')
insert into test (col1,col2) values ('a3cf9625-787a-4d91-a692-e2db8eb246a2', 'b7b7d1f9-b9cc-4201-bf75-40b0c4c2d301')

这是您可以在 LINQPad 中运行的完整程序:

void Main()
{
    Test<Test>();
}

static void Test<T>() where T : class
{
    PropertyInfo propertyInfo = typeof(T).GetProperty("col1");

    var parameter = Expression.Parameter(typeof(T), "x");

    //create where clause expression
    var orgIdMember = Expression.Property(parameter, "col1");
    ConstantExpression orgIdConstant = Expression.Constant(new Guid("d3aaaf86-d4e5-4d77-897e-1b36c1100448"), typeof(Guid));
    var orgIdBody = Expression.Equal(orgIdMember, orgIdConstant);
    var orgIdWhereExpression = Expression.Lambda<Func<Test, bool>>(orgIdBody, parameter); 

    TypedDataContext d = new TypedDataContext();

    var query = d.Tests.AsQueryable().Where(orgIdWhereExpression);

    //create start point expression 
    Guid startPoint = new Guid("09e0073a-be0e-405a-9564-d34056c8e42a");

    var startPointMember = Expression.Property(parameter, "col2");
    ConstantExpression startPointConstant = Expression.Constant(startPoint, startPoint.GetType());
    var left = Expression.Call(startPointMember, typeof(Guid).GetMethod("CompareTo", new Type[] { typeof(Guid) }), startPointConstant);
    var right = Expression.Constant(1);
    var startPointBody = Expression.Equal(left, right);
    var startPointWhereExpression = Expression.Lambda<Func<Test, bool>>(startPointBody, parameter);
    query = query.Where(startPointWhereExpression);

    MemberExpression keyMember = Expression.Property(parameter, "col1");
    var keyExpression = Expression.Lambda<Func<Test, Guid>>(keyMember, parameter);
    query = query.OrderBy(keyExpression);

    query.ToList().Dump();
}

这里有很多额外的废话,但我的问题的实际答案是我在哪里定义 left 变量和后面的 4 行。我在 LINQPad 中完成了所有这些操作,因为它具有 SQL 功能,它将这个 SQL 显示为输出:

-- Region Parameters
DECLARE @p0 UniqueIdentifier = '09e0073a-be0e-405a-9564-d34056c8e42a'
DECLARE @p1 UniqueIdentifier = 'd3aaaf86-d4e5-4d77-897e-1b36c1100448'
-- EndRegion
SELECT [t0].[col1] AS [Col1], [t0].[col2] AS [Col2]
FROM [test] AS [t0]
WHERE ([t0].[col2] > @p0) AND ([t0].[col1] = @p1)
ORDER BY [t0].[col1]

编辑:在创建表达式时,EF Core 的查询生成器中似乎存在错误/缺陷,例如我正在尝试使用 starPointWhereExpression 执行的操作。在我的测试中,似乎当它到达那个表达式时,它放弃并尝试对服务器执行它所拥有的东西,并保存任何进一步的表达式以供本地处理。我这样说是因为在运行这段代码之后,我添加了Skip()Take() 表达式。当比较 Guids 的 where 表达式在管道中时,skip 和 take 语句不在生成的 SQL 中。如果我不将其添加到管道中,那么这些语句会进入生成的 SQL。

编辑 2:我为此提交了a bug on github。这将在 EF Core 3 中解决。在撰写本文时(针对 EF Core 3 预览版 9 进行了测试),生成的 SQL 绝对是奇怪的,并且可能需要一些工作。

【讨论】:

    猜你喜欢
    • 2017-06-27
    • 1970-01-01
    • 2015-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-11
    • 2023-04-04
    • 1970-01-01
    相关资源
    最近更新 更多