У меня есть общий репозиторий, который использует общие выражения для возврата данных из 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));
        }
0
Help123 30 Май 2019 в 05:16

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);
0
NetMage 1 Июн 2019 в 16:05

Анонимные типы - это просто типы, которые 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);
0
Jeremy Lakeman 22 Янв 2020 в 00:02