.NET Core 中的动态 Linq 表达式
Dynamic Linq Expression in .NET Core
第一
我不能为此使用 Entity Framework,因为这是一个桌面应用程序,其中所有数据库调用首先通过 Web API 端点进行路由。此应用程序的要求是所有流量都是 HTTP 流量。
设置
假设我有一个 IEnumerable<BaseItem>
,其中 BaseItem
是所有模型继承自的基础模型 class。
我们还使用动态 class 和动态属性动态实例化并填充我们的集合,这些动态属性与 System.DataTable 的列名和适当的数据类型相匹配,return 从查询。
这些动态 classes 也继承自 BaseItem
,它公开了两个函数:
void SetValue(string PropertyName, object Value)
object GetValue(string PropertyName)
.
问题
我需要能够编写一个 linq 查询,根据查询引用的 table 中的唯一约束列动态地从集合中获取 selects 属性。我在一个单独的列表中有它,其中字符串值与动态 class.
上的动态 属性 名称匹配
我知道解决方案可能是使用表达式树,但我找到的所有示例都使用实际的 属性 引用,在这种情况下,属性 只能通过以下方式访问和设置GetValue 和 SetValue 函数。我很难思考如何实现这一目标。下面是我想要的 linq 查询的示例,如果我知道我想要的字段 return。
var Result = DefaultCollection.Select(x => new { BUSINESS_UNIT = x.GetValue("BUSINESS_UNIT"), FISCAL_YEAR = x.GetValue("FISCAL_YEAR") }).Distinct();
同样,我不能写这个,因为我不知道我需要在 select 列表中包含哪些字段,直到 运行我可以引用 UniqueConstraint 列表的时候。
如何使用 Linq Expression 动态实现此目的?
从头到尾示例
您已经从 System.Data.DataTable
填充了 IEnumerable<BaseItem>
,假设您 运行 的查询是 SELECT FIRST_NAME, LAST_NAME, EMAIL ADDRESS FROM TABLEA
。
IEnumerable 由自动化生成,其中生成动态 class 以表示 table 中的一行,并将动态属性添加到 class 以表示中的每一列table.
通过 BaseItem string result = item.GetValue("FIRST_NAME")
和 item.SetValue("EMAIL_ADDRESS", "MyEmail@Email.com');
中的方法访问属性
您还查询了 INFORMATION_SCHEMA
以获得对 TABLEA
的唯一约束,并将其存储在一个 public class ConstraintModel
对象中,该对象具有一个 public List<string> Columns
表示构成唯一约束。在这种情况下,约束中只有一列“EMAIL_ADDRESS”。
如果用户对 IEnumerable<BaseItem>
进行了更改并且不止一项具有相同的 EMAIL_ADDRESS,我们知道返回 SQL 的 UPDATE 语句将失败。我们要检查是否未违反唯一约束。
在用户保存时,我们希望运行 对内存中的 IEnumerable 对象进行“验证检查”。理想情况下,我们会编写一个 linq 查询,它基本上执行 SELECT SUM(1), EMAIL_ADDRESS GROUP BY EMAIL_ADDRESS,如果任何结果的值超过 1,我们知道重复存在。显然上面的语法是 SQL 而不是 Linq,但你明白了。我无法在编译时知道我需要 select 和分组依据的 BaseItem 上的哪些字段,因此我需要利用 Constraint.Columns 对象并使用它来编写动态 linq 表达式。此外,动态 Class 上的属性仅通过它们从 BaseItem 继承的方法公开... GetValue(string PropertyName)
和 SetValue(string PropertyName, object Value)
;
使用我的 EnumerableExpressionExt
class 的缩小版本帮助为 Enumerable
表达式构建 Expression
树,以及我的 ExpressionExt
助手 class:
public static class EnumerableExpressionExt {
private static Type TEnumerable = typeof(Enumerable);
private static Type TypeGenArg(this Expression e, int n) => e.Type.GetGenericArguments()[n];
public static MethodCallExpression Distinct(this Expression src) => Expression.Call(TEnumerable, "Distinct", new[] { src.TypeGenArg(0) }, src);
public static MethodCallExpression Select(this Expression src, LambdaExpression resultFne) => Expression.Call(TEnumerable, "Select", new[] { src.TypeGenArg(0), resultFne.ReturnType }, src, resultFne);
}
public static class ExpressionExt {
public static ConstantExpression AsConst<T>(this T obj) => Expression.Constant(obj, typeof(T));
public static LambdaExpression AsLambda(this Expression body) => Expression.Lambda(body);
public static LambdaExpression Lambda(this ParameterExpression p1, Expression body) => Expression.Lambda(body, p1);
public static NewExpression New(this Type t, params Expression[] vals) => Expression.New(t.GetConstructors()[0], vals, t.GetProperties());
public static ParameterExpression Param(this Type t, string pName) => Expression.Parameter(t, pName);
}
您可以创建自定义 Expression
扩展来翻译您的特定方法:
public static class MyExpressionExt {
public static MethodCallExpression GetValue(this Expression obj, string propName) =>
Expression.Call(obj, "GetValue", null, propName.AsConst());
}
您可以按如下方式构建 Expression
树:
// BaseItem x
var xParm = typeof(BaseItem).Param("x");
// typeof(new { object BUSINESS_UNIT, object FISCAL_YEAR })
var anonType = (new { BUSINESS_UNIT = default(object), FISCAL_YEAR = default(object) }).GetType();
// new { BUSINESS_UNIT = x.GetValue("BUSINESS_UNIT"), FISCAL_YEAR = x.GetValue("FISCAL_YEAR") }
var newExpr = anonType.New(xParm.GetValue("BUSINESS_UNIT"), xParm.GetValue("FISCAL_YEAR"));
// DefaultCollection.Select(x => newExpr)
var selExpr = DefaultCollection.AsConst().Select(xParm.Lambda(newExpr));
// DefaultCollection.Select(x => newExpr).Distinct()
var distExpr = selExpr.Distinct();
您可以像这样测试它(使用 LINQPad 的 Dump
方法):
// () => DefaultCollection.Select(x => newExpr).Distinct()
var f = distExpr.AsLambda();
var fc = f.Compile();
fc.DynamicInvoke().Dump();
LINQPad 也非常有助于输出编译器构建的 Expression
树并查看它们是如何构建的,或者 IQueryable<T>
查询 Expression
成员树。
注意:我喜欢不用打电话给 AsConst()
,所以我有更多的助手可以让这件事变得更容易。
读完你的 full example
听起来你根本不需要动态 linq 来做你想做的事情,但你确实需要知道主键是什么,为什么不呢尝试类似的东西:
更新我已经将简单的 pk 查找替换为 func PK 查找,以便可以使用复合主键。
List<string> primaryKeyColumns = new List<string> {"Id"}; // or whatever else
List<string> uniques = /// ConstraintModel.Columns;
List<BaseItem> baseItems = // your baseItems
// Now, wit
Func<BaseItem, BaseItem, bool> equalPrimaryKey = (left, right) => {
foreach(var pk in primaryKeyColumns) {
if(left.GetValue(pk) != right.GetValue(pk)) {
return false;
}
}
return true;
}; // or whatever else
var violators = baseItems.Select(x => {
return baseItems.Any(z => {
foreach(var unique in uniques) {
if (z.GetValue(unique) == x.GetValue(unique)
&& !equalPrimaryKey(x, z)) {
return true;
}
}
return false;
});
}).ToArray();
这将为您提供一组由任何唯一列复制的基本项目
第一
我不能为此使用 Entity Framework,因为这是一个桌面应用程序,其中所有数据库调用首先通过 Web API 端点进行路由。此应用程序的要求是所有流量都是 HTTP 流量。
设置
假设我有一个 IEnumerable<BaseItem>
,其中 BaseItem
是所有模型继承自的基础模型 class。
我们还使用动态 class 和动态属性动态实例化并填充我们的集合,这些动态属性与 System.DataTable 的列名和适当的数据类型相匹配,return 从查询。
这些动态 classes 也继承自 BaseItem
,它公开了两个函数:
void SetValue(string PropertyName, object Value)
object GetValue(string PropertyName)
.
问题
我需要能够编写一个 linq 查询,根据查询引用的 table 中的唯一约束列动态地从集合中获取 selects 属性。我在一个单独的列表中有它,其中字符串值与动态 class.
上的动态 属性 名称匹配我知道解决方案可能是使用表达式树,但我找到的所有示例都使用实际的 属性 引用,在这种情况下,属性 只能通过以下方式访问和设置GetValue 和 SetValue 函数。我很难思考如何实现这一目标。下面是我想要的 linq 查询的示例,如果我知道我想要的字段 return。
var Result = DefaultCollection.Select(x => new { BUSINESS_UNIT = x.GetValue("BUSINESS_UNIT"), FISCAL_YEAR = x.GetValue("FISCAL_YEAR") }).Distinct();
同样,我不能写这个,因为我不知道我需要在 select 列表中包含哪些字段,直到 运行我可以引用 UniqueConstraint 列表的时候。
如何使用 Linq Expression 动态实现此目的?
从头到尾示例
您已经从 System.Data.DataTable
填充了 IEnumerable<BaseItem>
,假设您 运行 的查询是 SELECT FIRST_NAME, LAST_NAME, EMAIL ADDRESS FROM TABLEA
。
IEnumerable 由自动化生成,其中生成动态 class 以表示 table 中的一行,并将动态属性添加到 class 以表示中的每一列table.
通过 BaseItem string result = item.GetValue("FIRST_NAME")
和 item.SetValue("EMAIL_ADDRESS", "MyEmail@Email.com');
您还查询了 INFORMATION_SCHEMA
以获得对 TABLEA
的唯一约束,并将其存储在一个 public class ConstraintModel
对象中,该对象具有一个 public List<string> Columns
表示构成唯一约束。在这种情况下,约束中只有一列“EMAIL_ADDRESS”。
如果用户对 IEnumerable<BaseItem>
进行了更改并且不止一项具有相同的 EMAIL_ADDRESS,我们知道返回 SQL 的 UPDATE 语句将失败。我们要检查是否未违反唯一约束。
在用户保存时,我们希望运行 对内存中的 IEnumerable 对象进行“验证检查”。理想情况下,我们会编写一个 linq 查询,它基本上执行 SELECT SUM(1), EMAIL_ADDRESS GROUP BY EMAIL_ADDRESS,如果任何结果的值超过 1,我们知道重复存在。显然上面的语法是 SQL 而不是 Linq,但你明白了。我无法在编译时知道我需要 select 和分组依据的 BaseItem 上的哪些字段,因此我需要利用 Constraint.Columns 对象并使用它来编写动态 linq 表达式。此外,动态 Class 上的属性仅通过它们从 BaseItem 继承的方法公开... GetValue(string PropertyName)
和 SetValue(string PropertyName, object Value)
;
使用我的 EnumerableExpressionExt
class 的缩小版本帮助为 Enumerable
表达式构建 Expression
树,以及我的 ExpressionExt
助手 class:
public static class EnumerableExpressionExt {
private static Type TEnumerable = typeof(Enumerable);
private static Type TypeGenArg(this Expression e, int n) => e.Type.GetGenericArguments()[n];
public static MethodCallExpression Distinct(this Expression src) => Expression.Call(TEnumerable, "Distinct", new[] { src.TypeGenArg(0) }, src);
public static MethodCallExpression Select(this Expression src, LambdaExpression resultFne) => Expression.Call(TEnumerable, "Select", new[] { src.TypeGenArg(0), resultFne.ReturnType }, src, resultFne);
}
public static class ExpressionExt {
public static ConstantExpression AsConst<T>(this T obj) => Expression.Constant(obj, typeof(T));
public static LambdaExpression AsLambda(this Expression body) => Expression.Lambda(body);
public static LambdaExpression Lambda(this ParameterExpression p1, Expression body) => Expression.Lambda(body, p1);
public static NewExpression New(this Type t, params Expression[] vals) => Expression.New(t.GetConstructors()[0], vals, t.GetProperties());
public static ParameterExpression Param(this Type t, string pName) => Expression.Parameter(t, pName);
}
您可以创建自定义 Expression
扩展来翻译您的特定方法:
public static class MyExpressionExt {
public static MethodCallExpression GetValue(this Expression obj, string propName) =>
Expression.Call(obj, "GetValue", null, propName.AsConst());
}
您可以按如下方式构建 Expression
树:
// BaseItem x
var xParm = typeof(BaseItem).Param("x");
// typeof(new { object BUSINESS_UNIT, object FISCAL_YEAR })
var anonType = (new { BUSINESS_UNIT = default(object), FISCAL_YEAR = default(object) }).GetType();
// new { BUSINESS_UNIT = x.GetValue("BUSINESS_UNIT"), FISCAL_YEAR = x.GetValue("FISCAL_YEAR") }
var newExpr = anonType.New(xParm.GetValue("BUSINESS_UNIT"), xParm.GetValue("FISCAL_YEAR"));
// DefaultCollection.Select(x => newExpr)
var selExpr = DefaultCollection.AsConst().Select(xParm.Lambda(newExpr));
// DefaultCollection.Select(x => newExpr).Distinct()
var distExpr = selExpr.Distinct();
您可以像这样测试它(使用 LINQPad 的 Dump
方法):
// () => DefaultCollection.Select(x => newExpr).Distinct()
var f = distExpr.AsLambda();
var fc = f.Compile();
fc.DynamicInvoke().Dump();
LINQPad 也非常有助于输出编译器构建的 Expression
树并查看它们是如何构建的,或者 IQueryable<T>
查询 Expression
成员树。
注意:我喜欢不用打电话给 AsConst()
,所以我有更多的助手可以让这件事变得更容易。
读完你的 full example
听起来你根本不需要动态 linq 来做你想做的事情,但你确实需要知道主键是什么,为什么不呢尝试类似的东西:
更新我已经将简单的 pk 查找替换为 func PK 查找,以便可以使用复合主键。
List<string> primaryKeyColumns = new List<string> {"Id"}; // or whatever else
List<string> uniques = /// ConstraintModel.Columns;
List<BaseItem> baseItems = // your baseItems
// Now, wit
Func<BaseItem, BaseItem, bool> equalPrimaryKey = (left, right) => {
foreach(var pk in primaryKeyColumns) {
if(left.GetValue(pk) != right.GetValue(pk)) {
return false;
}
}
return true;
}; // or whatever else
var violators = baseItems.Select(x => {
return baseItems.Any(z => {
foreach(var unique in uniques) {
if (z.GetValue(unique) == x.GetValue(unique)
&& !equalPrimaryKey(x, z)) {
return true;
}
}
return false;
});
}).ToArray();
这将为您提供一组由任何唯一列复制的基本项目