У меня есть общий репозиторий, который использует общие выражения для возврата данных из 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);
Вышеупомянутая лямбда-функция возвращает только одно свойство из класса Ingredient. Можно ли создать лямбду времени выполнения, которая возвращает анонимный объект, используя деревья выражений? Т.е.
x => new { x.Id, x.Name }
Обратите внимание, что пользователи могут запрашивать разные поля (например, Name, Description, DateCreated и т. Д.), Поэтому необходимо динамически создавать лямбда-выражение.
Я знаю, что могу использовать 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));
}
2 ответа
Это упрощенный создатель анонимного типа. Он использует открытые поля вместо свойств здания, у него нет реализованных методов (он получает конструктор по умолчанию).
Во-первых, простой метод расширения, который я использую позже:
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);
Новые вопросы
c#
C # (произносится как «резкий») - это высокоуровневый, статически типизированный язык программирования с несколькими парадигмами, разработанный Microsoft. Код C # обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, включая, среди прочего, .NET Framework, .NET Core и Xamarin. Используйте этот тег для вопросов о коде, написанном на C # или в формальной спецификации C #.