Как я могу создать динамический выбор нескольких свойств на IEnumerable <T>во время выполнения? - PullRequest
6 голосов
/ 25 января 2012

Я задал очень похожий вопрос вчера, но только сегодня я понял, что ответ, который я принял, не решает всех моих проблем.У меня есть следующий код:

public Expression<Func<TItem, object>> SelectExpression<TItem>(string fieldName)
{
    var param = Expression.Parameter(typeof(TItem), "item");
    var field = Expression.Property(param, fieldName);
    return Expression.Lambda<Func<TItem, object>>(field, 
        new ParameterExpression[] { param });
}

, который используется следующим образом:

string primaryKey = _map.GetPrimaryKeys(typeof(TOriginator)).Single();
var primaryKeyExpression = SelectExpression<TOriginator>(primaryKey);
var primaryKeyResults = query.Select(primaryKeyExpression).ToList();

Это позволяет мне извлечь первичные ключи из IQueryable<TUnknown>.Проблема в том, что этот код работает только с одним первичным ключом, и мне нужно добавить поддержку нескольких PK.

Итак, есть ли способ, как я могу адаптировать метод SelectExpression, описанный выше, чтобы взять IEnumerable<string> (это мой список имен свойств первичного ключа) и заставить метод вернуть выражение, которое выбирает эти ключи?

Т.е. с учетом следующего:

var knownRuntimePrimaryKeys = new string[] { "CustomerId", "OrderId" }`

Для My Select необходимо выполнить следующее (во время выполнения):

var primaryKeys = query.Select(x=> new { x.CustomerId, x.OrderId });

Ответы [ 3 ]

3 голосов
/ 25 января 2012

Нет простого способа сделать именно то, что вы хотите, потому что это потребует от вас динамического создания нового типа (анонимные типы создаются компилятором, когда они известны статически).Хотя это, безусловно, выполнимо, это, вероятно, не самый простой вариант ...

Подобного результата можно достичь, используя кортежи :

public Expression<Func<TItem, object>> SelectExpression<TItem>(string[] propertyNames)
{
    var properties = propertyNames.Select(name => typeof(TItem).GetProperty(name)).ToArray();
    var propertyTypes = properties.Select(p => p.PropertyType).ToArray();
    var tupleTypeDefinition = typeof(Tuple).Assembly.GetType("System.Tuple`" + properties.Length);
    var tupleType = tupleTypeDefinition.MakeGenericType(propertyTypes);
    var constructor = tupleType.GetConstructor(propertyTypes);
    var param = Expression.Parameter(typeof(TItem), "item");
    var body = Expression.New(constructor, properties.Select(p => Expression.Property(param, p)));
    var expr = Expression.Lambda<Func<TItem, object>>(body, param);
    return expr;
}
2 голосов
/ 25 января 2012

Вы можете использовать Tuple<>, потому что анонимные типы должны быть известны во время компиляции:

public Expression<Func<TItem, object>> SelectExpression<TItem>(params string[] fieldNames)
{
    var param = Expression.Parameter(typeof(TItem), "item");
    var fields = fieldNames.Select(x => Expression.Property(param, x)).ToArray();
    var types = fields.Select(x => x.Type).ToArray();
    var type = Type.GetType("System.Tuple`" + fields.Count() + ", mscorlib", true);
    var tuple = type.MakeGenericType(types);
    var ctor = tuple.GetConstructor(types);
    return Expression.Lambda<Func<TItem, object>>(
        Expression.New(ctor, fields), 
        param
    );
}

, а затем:

var primaryKeyExpression = SelectExpression<TOriginator>("CustomerId", "OrderId");

сгенерирует следующее выражение:

item => new Tuple<string, string>(item.CustomerId, item.OrderId)
1 голос
/ 25 января 2012

Как уже указывалось, вы, по сути, пытаетесь создать анонимный тип, созданный во время выполнения, который не будет работать.

Есть ли альтернатива использованию анонимного типа и до сих пордостичь того, что мне нужно?

Это действительно зависит от того, что вы имеете в виду.Очевидные ответы - использовать конструкции времени выполнения, такие как словарь, кортеж и т. Д. Но вы, вероятно, полностью знаете об этом сами, поэтому я предполагаю, что вы хотите получить результат времени компиляции с реальными именами полей, чтобы любойнеправильное использование primaryKeys обнаруживается во время компиляции.

Если это так, то, боюсь, вы можете генерировать соответствующий код перед компиляцией.Это не так плохо, как может показаться, но не совсем прозрачно: когда вы меняете схему, вам нужно как-то перезапустить генерацию кода.

В нашей компании мы сделали именно это,вдохновленный SubSonic , но обнаруживший, что сам SubSonic не совсем то, что мы хотели.На мой взгляд, все получилось неплохо.

...