[英]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.