[英]EF Linq- Dynamic Lambda expression trees
我有一个通用存储库,它使用通用表达式从 Entity Framework Core 返回数据。
public async Task<T2> GetFieldsAsync<T2>(Expression<Func<T, T2>> expression)
{
return await context.Set<T>()
.Select(expression)
.FirstOrDefaultAsync();
}
现在,如果我想选择特定字段,在编译时我可以编写以下语句:
var test = await _repositoryBase.GetFieldsAsync(x => new { x.Id, x.Name });
我希望能够在运行时执行上述操作。 我可以在运行时创建一个返回单个参数的表达式,如下所示:
var expression = Expression.Parameter(typeof(Ingredient), "ingr");
var param1 = Expression.Property(expression, "Id");
var lambda = Expression.Lambda<Func<Ingredient, Guid>>(param1, expression);
var test = await _repositoryBase.GetFieldsAsync(lambda);
上面的 lambda 只从 Ingredient 类返回一个属性。 是否可以创建一个使用表达式树返回匿名对象的运行时 lambda? IE
x => new { x.Id, x.Name }
请注意,用户可能会请求不同的字段(例如 Name、Description、DateCreated 等),因此需要动态创建 lambda 表达式。
我知道我可以使用https://github.com/StefH/System.Linq.Dynamic.Core通过其内置的 IQueryable 扩展方法传入字符串以选择语句。 我想知道是否有办法在运行时通过用户传入的字段列表动态选择特定字段。
目前,我的方法是从数据库中获取类的所有属性,并使用 ExpandoObject 仅选择用户请求的字段。 这里的问题是,虽然它有效,但我从数据库返回的数据比所需的多。 我想通过只选择所需的数据来避免这种情况。
//user did not provide any fields so include all fields
if (string.IsNullOrEmpty(field))
{
myPropertyInfoList.AddRange(typeProperties);
}
else
{
foreach (var item in fields)
{
myPropertyInfoList.Add(type.GetProperty(item, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance));
}
var expandoObj = (IDictionary<string, object>)new ExpandoObject();
foreach (var item in myPropertyInfoList)
{
expandoObj.Add(item.Name, item.GetValue(ingrView));
}
这是一个简化的匿名类型创建器。 它使用公共字段而不是构建属性,它没有实现任何方法(它有一个默认的构造函数)。
先说一个我后面用的简单的扩展方法:
public static class StringExt {
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings);
}
这里是从描述匿名类型的Dictionary<string,Type>
创建新匿名类型的代码。 生成具有每个字段(属性)类型的类型参数的泛型类型,然后将其固定为正在使用的实际类型 - 这就是 C# 编译器生成匿名类型的方式。 此代码创建(一次)动态程序集和模块,然后根据需要添加新类型。 它使用缓存来(尝试)防止多次创建相同的类型。
public static class AnonymousExt {
private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes), new object[0]);
private static ModuleBuilder AnonTypeMB;
private static int AssemCount = 25;
// create a pseudo anonymous type (POCO) from an IDictionary of property names and values
// using public fields instead of properties
// no methods are defined on the type
public static Type MakeAnonymousType(IDictionary<string, Type> objDict) {
// find or create AssemblyBuilder for dynamic assembly
if (AnonTypeMB == null) {
var assemblyName = new AssemblyName($"MyDynamicAssembly{AssemCount}");
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
AnonTypeMB = ab.DefineDynamicModule("MyDynamicModule");
}
// get a dynamic TypeBuilder
var typeBuilder = AnonTypeMB.DefineType($"<>f__AnonymousType{AssemCount++}`{objDict.Keys.Count}", TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit);
typeBuilder.SetCustomAttribute(CompilerGeneratedAttributeBuilder);
// create generic parameters for every field
string gtpName(string fieldName) => $"<{fieldName}>j__TPar";
var gtpnames = objDict.Keys.Select(k => gtpName(k)).ToArray();
var gtpbs = typeBuilder.DefineGenericParameters(gtpnames);
var gtpN2Bs = gtpnames.Zip(gtpbs, (n, pb) => new { n, pb }).ToDictionary(g => g.n, g => g.pb);
// add public fields to match the source object
var fbs = new List<FieldBuilder>();
foreach (var srcFieldName in objDict.Keys)
fbs.Add(typeBuilder.DefineField(srcFieldName, gtpN2Bs[gtpName(srcFieldName)], FieldAttributes.Public));
// Fix the generic class
var fieldTypes = objDict.Values.ToArray();
return typeBuilder.CreateType().MakeGenericType(fieldTypes);
}
static string MakeAnonymousTypeKey(IDictionary<string, Type> objDict) => objDict.Select(d => $"{d.Key}~{d.Value}").Join("|");
public static Dictionary<string, Type> PrevAnonTypes = new Dictionary<string, Type>();
public static Type FindOrMakeAnonymousType(IDictionary<string, Type> objDict) {
var wantedKey = MakeAnonymousTypeKey(objDict);
if (!PrevAnonTypes.TryGetValue(wantedKey, out var newType)) {
newType = MakeAnonymousType(objDict);
PrevAnonTypes[wantedKey] = newType;
}
return newType;
}
}
这里是用它与SQL表列举了一些示例代码Accounts
具有名为类类型Accounts
。 因为我的匿名类型没有接受字段值的构造函数,所以我使用MemberInitExpression
而不是普通的NewExpression
(无论如何它看起来不必要地复杂?)。
var objDescr = new Dictionary<string, Type> { { "Actid", typeof(Int32) }, { "Application", typeof(string) }, { "Username", typeof(string) }};
var aType = AnonymousExt.FindOrMakeAnonymousType(objDescr);
var parma = Expression.Parameter(typeof(Accounts), "a");
var fxbe = aType.GetFields().Select(fi => Expression.Bind(fi, Expression.Field(parma, fi.Name))).ToArray();
var mie = Expression.MemberInit(Expression.New(aType), fxbe);
var myf = Expression.Lambda<Func<Accounts, object>>(mie, parma);
var ans = Accounts.Select(myf).Take(2);
匿名类型只是 C# 在编译时为您构建的类型。 与其尝试构建匿名类型,不如为生成的查询的每一行返回一个object[]
。 作为奖励,这降低了处理返回数据的复杂性。
List<string> properties = ... ;
var parameter = Expression.Parameter(typeof(T), "e");
var selectExpression = Expression.Lambda<Func<T, object[]>>(
Expression.NewArrayInit(
typeof(object),
properties.Select(p =>
{
var ret = Expression.Property(parameter, p);
if (ret.Type != typeof(object))
ret = Expression.Convert(ret, typeof(object));
return ret;
})
),
parameter);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.