如何解决.NET Core中的动态Linq表达式
第一
我不能为此使用实体框架,因为这是一个桌面应用程序,在该应用程序中,所有数据库调用都首先通过Web API端点进行路由。此应用程序的要求是所有流量都是HTTP流量。
设置
可以说我有一个IEnumerable<BaseItem>
,其中BaseItem
是所有模型都继承的基础模型类。
我们还使用与查询返回的System.DataTable的列名和适当的数据类型相匹配的动态类和动态属性动态实例化并填充我们的集合。
这些动态类也继承自BaseItem
,它公开了两个功能:void SetValue(string PropertyName,object Value)
object GetValue(string PropertyName)
。
问题
我需要能够编写一个linq查询,该查询根据查询所引用表中的唯一约束列从集合中动态选择属性。我将其放在单独的列表中,其中字符串值与动态类上的动态属性名称匹配。
我知道解决方案可能是使用表达式树,但是我发现的所有示例都使用实际的属性引用,在这种情况下,只能通过GetValue和SetValue函数访问和设置属性。我很难解决这个问题。下面是我想要的linq查询的示例,如果我知道要返回的字段。
var Result = DefaultCollection.Select(x => new { BUSINESS_UNIT = x.GetValue("BUSINESS_UNIT"),FISCAL_YEAR = x.GetValue("FISCAL_YEAR") }).Distinct();
同样,我无法编写此代码,因为在运行时可以引用UniqueConstraint列表之前,我不知道需要在选择列表中包括哪些字段。
如何使用Linq Expression动态实现此目标?
从头开始示例
您已经从IEnumerable<BaseItem>
中填充了System.Data.DataTable
,可以说您运行的查询是SELECT FIRST_NAME,LAST_NAME,EMAIL ADDRESS FROM TABLEA
。
IEnumerable是通过自动化来填充的,其中会生成一个动态类来表示表中的一行,并将动态属性添加到该类中以表示表中的每一列。
通过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查询,由essentailly执行SELECT SUM(1),即EMAIL_ADDRESS GROUP BY EMAIL_ADDRESS,如果任何结果的值均大于1,则说明存在重复项。显然,上面的语法不是SQL的Linq,而是您的主意。我不知道在编译时需要在BaseItem上的哪些字段进行选择和分组,因此我需要利用Constraint.Columns对象并使用它编写动态linq表达式。此外,动态类的属性仅通过它们从BaseItem ... GetValue(string PropertyName)
和SetValue(string PropertyName,object Value)
继承的方法公开;
解决方法
使用我的EnumerableExpressionExt
类的简化版本来帮助为Expression
表达式和Enumerable
助手类构建ExpressionExt
树:
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查找替换为一个功能性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();
这将为您提供一系列基础项目,这些基础项目可以被任何唯一列重复
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。