Reputation: 1239
I wrote a library that can serialize LINQ expressions to a byte array. A server can receive and reconstruct the query from the serialized data, plug in the appropriate data sources, and return the result to the client. The library supports projection to anonymous types. When the query is de-serialized, it reconstructs the anonymous RuntimeType
by emitting the appropriate IL. When the client receives the result, there's an expectation that the type can be accessed using the anonymous type's properties.
E.g.
var query = Customers
.Where(c => c.State == "CO")
.Select(c => new { c.Id, c.FirstName, c.LastName });
var results = query.ToListAsync(cancellationToken);
foreach(var result in results)
{
Console.WriteLine($"{c.Id} = {c.FirstName} {c.LastName}";
}
The underlying query provider can't present the result because the anonymous type created by IL emit is not the same type as what was defined in the query and so List<<>__anonymous1>
is not convertible to `List<<>__anonymous2>. Therefore the type needs to be converted. The CastByExample trick here won't work because the type's aren't in the same assembly.
How can this be achieved?
Upvotes: 0
Views: 180
Reputation: 1239
There's two options here:
public static Func<object, TResult> CreateAnonymousTypeMapper<TResult>(Type srcType)
{
/* Produces the following equivalent in delegate form:
* TResult Convert(object obj)
* {
* var source = (SourceType)obj;
* return new TResult(source.Property1, source.Property2, ...);
* }
*/
var destType = typeof(TResult);
var destConstructor = destType.GetConstructors()[0];
var constructorParams = destConstructor.GetParameters();
var sourceProperties = srcType.GetProperties().ToDictionary(p => p.Name);
var sourceParameterExpression = Expression.Parameter(typeof(object), "obj");
var typedSourceParameterExpression = Expression.Parameter(srcType, "source");
var convertObjToSourceExpression = Expression.Convert(sourceParameterExpression, srcType);
var assignToSourceTypeExpression = Expression.Assign(
typedSourceParameterExpression,
convertObjToSourceExpression);
var callConstructorArgumentExpressions = constructorParams
.Select(param => Expression.Property(
typedSourceParameterExpression,
sourceProperties[param.Name!]))
.Cast<Expression>()
.ToArray();
var callConstructorExpression = Expression.New(destConstructor, callConstructorArgumentExpressions);
var returnTarget = Expression.Label(destType);
var lambdaBodyExpression = Expression.Block(new[] { typedSourceParameterExpression },
assignToSourceTypeExpression,
Expression.Label(returnTarget, callConstructorExpression));
var lambdaExpression = Expression.Lambda<Func<object, TResult>>(
lambdaBodyExpression,
sourceParameterExpression);
return lambdaExpression.Compile();
}
Upvotes: 1