I'm trying to build an expression for creating a generic Tuple<> instance with a variable number of generic type arguments.
The idea for the generated Tuple<> instance is to dynamically create a composite key value for entity types based on properties that have a KeyAttribute on it. The composite key would then be used as a key in a Dictionary<object, TEntity>. So the lambda expression should be built for a certain entity type and the lambda would be called, passing an instance of TEntity to get back a composite key in the form of a Tuple<>.
Example entity model
public class MyEntityModel
{
[Key]
public string Key1 { get; set; }
[Key]
public Guid Key2 { get; set; }
public int OtherProperty { get; set; }
}
What expression should do
public Func<MyEntityModel, object> BuildKeyFactory()
{
// This is how the LambdaExpression should look like, but then for a generic entity type instead of fixed to MyEntityModel
return new Func<MyEntityModel, object>(entity => new Tuple<string, Guid>(entity.Key1, entity.Key2));
}
But of course the entity model needs to be a generic type.
What I have so far
public Func<TEntity, object> BuildKeyFactory<TEntity>()
{
var entityType = typeof(TEntity);
// Get properties that have the [Key] attribute
var keyProperties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.GetCustomAttribute(typeof(KeyAttribute)) != null)
.ToArray();
var tupleType = Type.GetType($"System.Tuple`{keyProperties.Length}");
if (tupleType == null) throw new InvalidOperationException($"No tuple type found for {keyProperties.Length} generic arguments");
var keyPropertyTypes = keyProperties.Select(x => x.PropertyType).ToArray();
var tupleConstructor = tupleType.MakeGenericType(keyPropertyTypes).GetConstructor(keyPropertyTypes);
if (tupleConstructor == null) throw new InvalidOperationException($"No tuple constructor found for key in {entityType.Name} entity");
// The following part is where I need some help with...
var newTupleExpression = Expression.New(tupleConstructor, keyProperties.Select(x => ????));
return Expression.Lambda<Func<TEntity, object>>(????).Compile();
}
As you can see, I'm not able to figure out how I need to create property expressions for passing to the Expression.New() call (probably something with Expression.MakeMemberAccess(Expression.Property()) but don't know how to pass the TEntity instance from the lambda argument) and how I can 'chain' this with the Expression.Lambda call. Any help would be highly appreciated!