Generate e => new { e.Id, e.CompanyId } with Expre

2019-05-22 21:21发布

问题:

This question is a continuation of this question here. If someone cares to know why I need to do things like this, you can find the rationale in that question. Not that it matters, really.

I need a method like this:

public virtual Expression<Func<T, object>> UpdateCriterion()
{
    // this doesn't work because the compiler doesn't know if T has Id & CompanyId
    return e => new { e.Id, e.CompanyId }; 
}

The problem is, there is no supertype for T that I could use to pull out Id and CompanyId from, I have to do it dynamically. Thanks to the answer to that referenced question, I have successfully built and used this kind of method for one property (e => e.Id), but I'm having issues implementing it for two. Just for visibility, the solution for one field is:

public virtual Expression<Func<T, object>> UpdateCriterion()
{
    var param = Expression.Parameter(typeof(T));
    var body = Expression.Convert(Expression.Property(param, "ID"), typeof(object));

    return Expression.Lambda<Func<T, object>>(body, param);
}

I've been going nuts with this for over 6 hours... So, how do I solve this?

回答1:

The Body of this Lamba is a MemerInitExpression.

That was the easy part. The bigger Problem here is that you use an Anonymous Type in your Lambda.

Expression<Func<TranslatedText, object>> exp;
exp = p => new { p.LanguageId, p.TextId};

If you use such an AnonymousType, the Compiler will inspect your code, detect the AnonymousType declaration and will create a Type like this on the fly.

public class f__AnonymousType0
{
    public int LanguageId { get; set; }
    public int TextId { get; set; }
}

And change your lambda into something like this.

exp = p => new f__AnonymousType0 { LanguageId = p.LanguageId, TextId = p.TextId };

Because you would like to create the lambda at runtime the f__AnonymousType0 type you need for the MemberInitExpression does not exist.

As you need an actual Type to create this Expression you have two options to get one.

1 - Write some generic classes like the Tuple class of the .NET Framework. Of cores this solution is limited to a maximum amount of properties.

pro: easy to create and use – con: limited property count.

public class KeyTuple<T1, T2>
{
    public T1 Item1 { get; set; }
    public T2 Item2 { get; set; }
}

public class KeyTuple<T1, T2, T3>
{
    public T1 Item1 { get; set; }
    public T2 Item2 { get; set; }
    public T3 Item3 { get; set; }
}

public class KeyTuple<T1, T2, T3>
public class KeyTuple<T1, T2, T3, T4>
public class KeyTuple<T1, T2, T3, T4, T5>
public class KeyTuple<T1, T2, T3, T4, T5, T6>

2 - You could use Reflection.Emit and create a type at runtime http://www.codeproject.com/Articles/121568/Dynamic-Type-Using-Reflection-Emit

pro: unlimited property count – con: complicated

When you have a type you can use the Expression tree api you already know to crate the lambda

var keys = new[] { "LanguageId", "TextId" };

var param = Expression.Parameter(typeof(TranslatedText));
var properties = keys.Select(p => Expression.Property(param, p)).ToList();

var keyTupleType = typeof(KeyTuple<,>).Assembly.GetType(string.Format("AnonymousTypeExpression.KeyTuple`{0}",keys.Count()));
keyTupleType = keyTupleType.MakeGenericType(properties.Select(p => p.Type).ToArray());

var bindings = properties.Select((p,i) => Expression.Bind(keyTupleType.GetProperty(string.Format("Item{0}",i + 1)),p)).ToArray();
var body = Expression.MemberInit(Expression.New(keyTupleType), bindings);
var result= Expression.Lambda<Func<TranslatedText, object>>(body, param);

This creates an expression that looks like this

exp = p => new KeyTuple<int, int> { Item1 = p.LanguageId, Item2 = p.TextId };


回答2:

Try using reflection ?

Please not i don't have visual studio or any other IDE in front of me so the code my contain some bugs and typos

Also notes if you don't need properties, you may need fields there are also GetField() and GetFields() look them around.

public virtual Expression<Func<T, object>> UpdateCriterion()
{

    return e => new { GetPropertyValue<T>(e,"id"), GetPropertyValue<T>(e,"CompanyId") }; 
}

public object GetPropertyValue<T>(T TargetObject,string PropertyName)
{
     var prop = typeof(T).GetProperty(PropertyName).GetValue(TargetObject, null);
}


标签: c# .net lambda