How can i do DRY for many similar joins in LINQ? - c#

I have an interface with ten fields and i often join two Queryables of this interface. Most of the time all of the ten fields have to be equal but sometimes not. Right now i solve this by having a lot of static extensionmethods but they all look kinda the same and with every variant i add i fear i will add an error (Wrong fields etc...).
public static class MyJoins
{
public static IQueryable<T> JoinOnAll<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F2, a.F3, a.F4, a.F5, a.F6, a.F7, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F2, b.F3, b.F4, b.F5, b.F6, b.F7, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
public static IQueryable<T> JoinOnAllButF2<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F3, a.F4, a.F5, a.F6, a.F7, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F3, b.F4, b.F5, b.F6, b.F7, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
public static IQueryable<T> JoinOnAllButF4F7<T, TA, TB>(this IQueryable<TA> query, IQueryable<TB> otherQuery)
where TA : IMyInterface where TB : IMyInterface where T : class, IJoinInterface<TA, TB>, new()
{
return query.Join(otherQuery,
a => new { a.F1, a.F2, a.F3, a.F5, a.F6, a.F8, a.F9, a.F10 },
b => new { b.F1, b.F2, b.F3, b.F5, b.F6, b.F8, b.F9, b.F10 },
(t, p) => new T { AA = a, BB = b });
}
//and many more of this
}
I there a way to pass the fields to compare on as a parameter? (I think there is not)
Are there other ways to solve this mass of nearly duplicated code?
----How i would use this-------------------------
I mainly use it in a way where i split querys, join them and then concat them:
IQueryable a = ......
IQueryable b = ......
var firstPart = a.Where(MyExpression).JoinOnAll(b);
var secondPart = a.Where(OtherExpression).JoinOnAllButF2(b);
var thirdPart = a.Where(AnotherExpression).JoinOnAllButF1F2F8(b);
var joinResult = firstPart.Concat(secondpart).Concat(thirdPart);
var joinResultFiltered = joinResult.WHere(AndAnotherExpression);
return joinResultFiltered;
I have many functions like this but the Expressions and joins are always different.
Additional Info because someone in the Commetns asked:
The interfaces are basically just this
public interface IMyInterface{
public string F1 {get;set;}
public string F2 {get;set;}
// up to F10
}

How about something like this:
public static class MyJoins
{
private sealed class ReplacementVisitor : ExpressionVisitor
{
public ReplacementVisitor(LambdaExpression source, Expression toFind, Expression replaceWith)
{
SourceParameters = source.Parameters;
ToFind = toFind;
ReplaceWith = replaceWith;
}
private IReadOnlyCollection<ParameterExpression> SourceParameters { get; }
private Expression ToFind { get; }
private Expression ReplaceWith { get; }
private Expression ReplaceNode(Expression node) => node == ToFind ? ReplaceWith : node;
protected override Expression VisitConstant(ConstantExpression node) => ReplaceNode(node);
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
private static Expression<Func<T, object>> BuildJoinFields<T>(LambdaExpression fn) where T : IMyInterface
{
var p = Expression.Parameter(typeof(T), "p");
var visitor = new ReplacementVisitor(fn, fn.Parameters[0], p);
var body = visitor.Visit(fn.Body);
return Expression.Lambda<Func<T, object>>(body, p);
}
public static IQueryable<T> JoinOn<T, TA, TB>(
this IQueryable<TA> query,
IQueryable<TB> otherQuery,
Expression<Func<IMyInterface, object>> fieldsToJoinOn)
where TA : IMyInterface
where TB : IMyInterface,
where T : class, IJoinInterface<TA, TB>, new()
{
Expression<Func<TA, object>> aFields = BuildJoinFields<TA>(fieldsToJoinOn);
Expression<Func<TB, object>> bFields = BuildJoinFields<TB>(fieldsToJoinOn);
return query.Join(otherQuery, aFields, bFields, (a, b) => new T { AA = a, BB = b });
}
}
query.JoinOn(otherQuery, x => new { x.F1, x.F2, x.F3, ... })
Although I'm not convinced the compiler will be able to infer the type for type parameter T.

Another solution. It does not care about interfaces, join key is based on outer type. And if property not found inner type, it will throw exception.
public static class QueryableExtensions
{
public interface IJoinInterface<TA, TB>
{
public TA AA { get; set; }
public TB BB { get; set; }
}
class JoinHandler<TA, TB> : IJoinInterface<TA, TB>
{
public TA AA { get; set; }
public TB BB { get; set; }
}
public static IQueryable<IJoinInterface<TA, TB>> JoinOn<TA, TB, TKey>(
this IQueryable<TA> outer,
IQueryable<TB> inner,
Expression<Func<TA, TKey>> joinKey)
{
var innerParam = Expression.Parameter(typeof(TB), "inner");
var innerKey = BuildKey(joinKey, innerParam);
Expression<Func<TA, TB, IJoinInterface<TA, TB>>> resultExpression = (a, b) => new JoinHandler<TA, TB> {AA = a, BB = b};
var queryExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Join),
new[] { typeof(TA), typeof(TB), typeof(TKey), typeof(IJoinInterface<TA, TB>) }, outer.Expression, inner.Expression,
joinKey, innerKey, resultExpression);
return outer.Provider.CreateQuery<IJoinInterface<TA, TB>>(queryExpression);
}
private static LambdaExpression BuildKey(LambdaExpression source, ParameterExpression param)
{
var body = new MemberReplacer(source.Parameters[0], param).Visit(source.Body);
return Expression.Lambda(body, param);
}
class MemberReplacer : ExpressionVisitor
{
public MemberReplacer(ParameterExpression sourceParam, ParameterExpression destParam)
{
SourceParam = sourceParam;
DestParam = destParam;
}
public ParameterExpression SourceParam { get; }
public ParameterExpression DestParam { get; }
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == SourceParam)
{
if (DestParam.Type == SourceParam.Type)
return node.Update(DestParam);
var destProp = DestParam.Type.GetProperty(node.Member.Name);
if (destProp == null)
throw new ArgumentException($"Type '{DestParam.Type.Name}' has no property '{node.Member.Name}'.");
return Expression.MakeMemberAccess(DestParam, destProp);
}
return base.VisitMember(node);
}
}
}
Solution is closer to previous answer, but more universal and do not care about specific interfaces realizations.

Related

Rebuild Expression

I have an expression like: Expression<Func<TheObject, int, bool>> myExpression = (myObj, theType) => { myObj.Prop > theType };
I need to dynamically rebuild myExpression to a new expression of type Expression<Func<TheObject, bool>> and replace “theType” parameter from the first expression with a concrete value 123 like:
Expression<Func<TheObject, bool>> myNewExpression = myObj => { myObj.Prop > 123 };
How can I do that?
Br
Philip
With a simple expression replacer it is quite easy:
This is the one I've written for myself... supports simple replaces and multiple replaces.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
// A simple expression visitor to replace some nodes of an expression
// with some other nodes. Can be used with anything, not only with
// ParameterExpression
public class SimpleExpressionReplacer : ExpressionVisitor
{
public readonly Dictionary<Expression, Expression> Replaces;
public SimpleExpressionReplacer(Expression from, Expression to)
{
Replaces = new Dictionary<Expression, Expression> { { from, to } };
}
public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
{
// Note that we should really clone from and to... But we will
// ignore this!
Replaces = replaces;
}
public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
{
Replaces = new Dictionary<Expression, Expression>();
using (var enu1 = from.GetEnumerator())
using (var enu2 = to.GetEnumerator())
{
while (true)
{
bool res1 = enu1.MoveNext();
bool res2 = enu2.MoveNext();
if (!res1 || !res2)
{
if (!res1 && !res2)
{
break;
}
if (!res1)
{
throw new ArgumentException("from shorter");
}
throw new ArgumentException("to shorter");
}
Replaces.Add(enu1.Current, enu2.Current);
}
}
}
public override Expression Visit(Expression node)
{
Expression to;
if (node != null && Replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
Your TheObject
public class TheObject
{
public int Prop { get; set; }
}
Then you only need to replace the 2nd parameter in the body of the expression and rebuild the Expression<>
public class Program
{
public static void Main(string[] args)
{
Expression<Func<TheObject, int, bool>> myExpression = (myObj, theType) => myObj.Prop > theType;
int value = 123;
var body = myExpression.Body;
var body2 = new SimpleExpressionReplacer(myExpression.Parameters[1], Expression.Constant(value)).Visit(body);
Expression<Func<TheObject, bool>> myExpression2 = Expression.Lambda<Func<TheObject, bool>>(body2, myExpression.Parameters[0]);
}
}

How to correct replace type of Expression?

I have two classes:
public class DalMembershipUser
{
public string UserName { get; set; }
//other members
}
public class MembershipUser
{
public string UserName { get; set; }
//other members
}
I have function:
public IEnumerable<DalMembershipUser> GetMany(Expression<Func<DalMembershipUser, bool>> predicate)
{
//but here i can use only Func<MembershipUser, bool>
//so i made transformation
query = query.Where(ExpressionTransformer<DalMembershipUser,MembershipUser>.Tranform(predicate));
}
Current implementation:
public static class ExpressionTransformer<TFrom, TTo>
{
public class Visitor : ExpressionVisitor
{
private ParameterExpression _targetParameterExpression;
public Visitor(ParameterExpression parameter)
{
_targetParameterExpression = parameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _targetParameterExpression;
}
}
public static Expression<Func<TTo, bool>> Tranform(Expression<Func<TFrom, bool>> expression)
{
ParameterExpression parameter = Expression.Parameter(typeof(TTo), expression.Parameters[0].Name);
Expression body = expression.Body;
new Visitor(parameter).Visit(expression.Body);
return Expression.Lambda<Func<TTo, bool>>(body, parameter);
}
}
//Somewhere: .GetMany(u => u.UserName == "username");
Exception: Property 'System.String UserName' is not defined for type 'MembershipUser'
at line: new Visitor(parameter).Visit(expression.Body);
Finally it work. But still don't understand why clear parameters creation:
Expression.Parameter(typeof(TTo), from.Parameters[i].Name); not work and need to extract.
public static class ExpressionHelper
{
public static Expression<Func<TTo, bool>> TypeConvert<TFrom, TTo>(
this Expression<Func<TFrom, bool>> from)
{
if (from == null) return null;
return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from);
}
private static Expression<TTo> ConvertImpl<TFrom, TTo>(Expression<TFrom> from)
where TFrom : class
where TTo : class
{
// figure out which types are different in the function-signature
var fromTypes = from.Type.GetGenericArguments();
var toTypes = typeof(TTo).GetGenericArguments();
if (fromTypes.Length != toTypes.Length)
throw new NotSupportedException("Incompatible lambda function-type signatures");
Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
for (int i = 0; i < fromTypes.Length; i++)
{
if (fromTypes[i] != toTypes[i])
typeMap[fromTypes[i]] = toTypes[i];
}
// re-map all parameters that involve different types
Dictionary<Expression, Expression> parameterMap = new Dictionary<Expression, Expression>();
ParameterExpression[] newParams = GenerateParameterMap<TFrom>(from, typeMap, parameterMap);
// rebuild the lambda
var body = new TypeConversionVisitor<TTo>(parameterMap).Visit(from.Body);
return Expression.Lambda<TTo>(body, newParams);
}
private static ParameterExpression[] GenerateParameterMap<TFrom>(
Expression<TFrom> from,
Dictionary<Type, Type> typeMap,
Dictionary<Expression, Expression> parameterMap
)
where TFrom : class
{
var newParams = new ParameterExpression[from.Parameters.Count];
for (int i = 0; i < newParams.Length; i++)
{
Type newType;
if (typeMap.TryGetValue(from.Parameters[i].Type, out newType))
{
parameterMap[from.Parameters[i]] = newParams[i] = Expression.Parameter(newType, from.Parameters[i].Name);
}
}
return newParams;
}
class TypeConversionVisitor<T> : ExpressionVisitor
{
private readonly Dictionary<Expression, Expression> parameterMap;
public TypeConversionVisitor(Dictionary<Expression, Expression> parameterMap)
{
this.parameterMap = parameterMap;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// re-map the parameter
Expression found;
if (!parameterMap.TryGetValue(node, out found))
found = base.VisitParameter(node);
return found;
}
public override Expression Visit(Expression node)
{
LambdaExpression lambda = node as LambdaExpression;
if (lambda != null && !parameterMap.ContainsKey(lambda.Parameters.First()))
{
return new TypeConversionVisitor<T>(parameterMap).Visit(lambda.Body);
}
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression node)
{
// re-perform any member-binding
var expr = Visit(node.Expression);
if (expr.Type != node.Type)
{
if (expr.Type.GetMember(node.Member.Name).Any())
{
MemberInfo newMember = expr.Type.GetMember(node.Member.Name).Single();
return Expression.MakeMemberAccess(expr, newMember);
}
}
return base.VisitMember(node);
}
}
}
You need to use the result expression returned by the Visit method.
Just change:
Expression body = expression.Body;
new Visitor(parameter).Visit(expression.Body);
by
Expression body = new Visitor(parameter).Visit(expression.Body);

Custom Linq generator (AddDays) using NHibernate

I tried to include AddDays in NHibernate, like that :
public class ExtendedLinqtoHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public ExtendedLinqtoHqlGeneratorsRegistry()
{
this.Merge(new AddDaysGenerator());
}
}
public class AddDaysGenerator : BaseHqlGeneratorForMethod
{
public AddDaysGenerator()
{
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition<DateTimeOffset?>(d => d.Value.AddDays((double) 0))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
return treeBuilder.MethodCall("AddDays", visitor.Visit(targetObject).AsExpression(), visitor.Visit(arguments[0]).AsExpression());
}
}
public class MsSql2008CustomDialect : MsSql2008Dialect
{
public MsSql2008CustomDialect()
{
RegisterFunction("AddDays", new SQLFunctionTemplate(NHibernateUtil.DateTime, "dateadd(day,?2,?1)"));
}
}
And my NH configuration is :
configuration = new Configuration();
configuration.Proxy(p => p.ProxyFactoryFactory<DefaultProxyFactoryFactory>()).DataBaseIntegration(db =>
{
db.ConnectionStringName = "xxyy";
db.Dialect<MsSql2008CustomDialect>();
})
.AddAssembly(typeof(myClass).Assembly)
.CurrentSessionContext<LazySessionContext>()
.LinqToHqlGeneratorsRegistry<ExtendedLinqtoHqlGeneratorsRegistry>();
Trying to use that with predicates :
var predicate = PredicateBuilder.False<myClass>();
if(...)
predicate = predicate.Or(i => i.MyDate.AddDays(date) > DateTime.Today);
But its not working... I always got the same error :
System.NotSupportedException: System.DateTime AddDays(Double)
at NHibernate.Linq.Visitors.HqlGeneratorExpressionTreeVisitor.VisitMethodCallExpression(MethodCallExpression expression)
Any idea what am I doing wrong?
Thanks
As said in the comments, you should check that MyClass.MyDate is of type Nullable DateTimeOffset, which is the target type for your AddDays declaration :
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition<DateTimeOffset?>
(d => d.Value.AddDays((double) 0))
};

System.Linq.Expressions: Binding LambdaExpression inputs at runtime

Consider the following setup:
class A { public int x; }
class B { public int y; }
static class Helper
{
public static Expression<Func<B>> BindInput(
Expression<Func<A, B>> expression,
A input)
{
//TODO
}
}
static void Main(string[] args)
{
Expression<Func<B>> e = Helper.BindInput(
(A a) => new B { y = a.x + 3 },
new A { x = 4 });
Func<B> f = e.Compile();
Debug.Assert(f().y == 7);
}
What I want to do in the method BindInput is transform the expression to have input embedded into it. In the example usage in Main, the resulting expression e would be
() => new B { y = input.x + 3 }
where input is the second value that was passed into BindInput.
How would I go about doing this?
Edit:
I should add that the following expression e is not what I'm looking for:
((A a) => new B { y = a.x + 3 })(input)
This would be fairly trivial to obtain because it just involves adding a layer on top of the existing expression.
After lots of searching I stumbled across the magic ExpressionVisitor class. The following seems to be working perfectly:
class MyExpressionVisitor : ExpressionVisitor
{
public ParameterExpression TargetParameterExpression { get; private set; }
public object TargetParameterValue { get; private set; }
public MyExpressionVisitor(ParameterExpression targetParameterExpression, object targetParameterValue)
{
this.TargetParameterExpression = targetParameterExpression;
this.TargetParameterValue = targetParameterValue;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == TargetParameterExpression)
return Expression.Constant(TargetParameterValue);
return base.VisitParameter(node);
}
}
static class Helper
{
public static Expression<Func<B>> BindInput(Expression<Func<A, B>> expression, A input)
{
var parameter = expression.Parameters.Single();
var visitor = new MyExpressionVisitor(parameter, input);
return Expression.Lambda<Func<B>>(visitor.Visit(expression.Body));
}
}

Combine several similar SELECT-expressions into a single expression

How to combine several similar SELECT-expressions into a single expression?
private static Expression<Func<Agency, AgencyDTO>> CombineSelectors(params Expression<Func<Agency, AgencyDTO>>[] selectors)
{
// ???
return null;
}
private void Query()
{
Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };
using (RealtyContext context = Session.CreateContext())
{
IQueryable<AgencyDTO> agencies = context.Agencies.Select(CombineSelectors(selector3, selector4));
foreach (AgencyDTO agencyDTO in agencies)
{
// do something..;
}
}
}
Not simple; you need to rewrite all the expressions - well, strictly speaking you can recycle most of one of them, but the problem is that you have different x in each (even though it looks the same), hence you need to use a visitor to replace all the parameters with the final x. Fortunately this isn't too bad in 4.0:
static void Main() {
Expression<Func<Agency, AgencyDTO>> selector1 = x => new AgencyDTO { Name = x.Name };
Expression<Func<Agency, AgencyDTO>> selector2 = x => new AgencyDTO { Phone = x.PhoneNumber };
Expression<Func<Agency, AgencyDTO>> selector3 = x => new AgencyDTO { Location = x.Locality.Name };
Expression<Func<Agency, AgencyDTO>> selector4 = x => new AgencyDTO { EmployeeCount = x.Employees.Count() };
// combine the assignments from the 4 selectors
var convert = Combine(selector1, selector2, selector3, selector4);
// sample data
var orig = new Agency
{
Name = "a",
PhoneNumber = "b",
Locality = new Location { Name = "c" },
Employees = new List<Employee> { new Employee(), new Employee() }
};
// check it
var dto = new[] { orig }.AsQueryable().Select(convert).Single();
Console.WriteLine(dto.Name); // a
Console.WriteLine(dto.Phone); // b
Console.WriteLine(dto.Location); // c
Console.WriteLine(dto.EmployeeCount); // 2
}
static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
params Expression<Func<TSource, TDestination>>[] selectors)
{
var zeroth = ((MemberInitExpression)selectors[0].Body);
var param = selectors[0].Parameters[0];
List<MemberBinding> bindings = new List<MemberBinding>(zeroth.Bindings.OfType<MemberAssignment>());
for (int i = 1; i < selectors.Length; i++)
{
var memberInit = (MemberInitExpression)selectors[i].Body;
var replace = new ParameterReplaceVisitor(selectors[i].Parameters[0], param);
foreach (var binding in memberInit.Bindings.OfType<MemberAssignment>())
{
bindings.Add(Expression.Bind(binding.Member,
replace.VisitAndConvert(binding.Expression, "Combine")));
}
}
return Expression.Lambda<Func<TSource, TDestination>>(
Expression.MemberInit(zeroth.NewExpression, bindings), param);
}
class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression from, to;
public ParameterReplaceVisitor(ParameterExpression from, ParameterExpression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == from ? to : base.VisitParameter(node);
}
}
This uses the constructor from the first expression found, so you might want to sanity-check that all of the others use trivial constructors in their respective NewExpressions. I've left that for the reader, though.
Edit: In the comments, #Slaks notes that more LINQ could make this shorter. He is of course right - a bit dense for easy reading, though:
static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
params Expression<Func<TSource, TDestination>>[] selectors)
{
var param = Expression.Parameter(typeof(TSource), "x");
return Expression.Lambda<Func<TSource, TDestination>>(
Expression.MemberInit(
Expression.New(typeof(TDestination).GetConstructor(Type.EmptyTypes)),
from selector in selectors
let replace = new ParameterReplaceVisitor(
selector.Parameters[0], param)
from binding in ((MemberInitExpression)selector.Body).Bindings
.OfType<MemberAssignment>()
select Expression.Bind(binding.Member,
replace.VisitAndConvert(binding.Expression, "Combine")))
, param);
}
If all of the selectors will only initialize AgencyDTO objects (like your example), you can cast the expressions to NewExpression instances, then call Expression.New with the Members of the expressions.
You'll also need an ExpressionVisitor to replace the ParameterExpressions from the original expressions with a single ParameterExpression for the expression you're creating.
In case anyone else stumbles upon this with a similar use case as mine (my selects targeted different classes based on the level of detail needed):
Simplified scenario:
public class BlogSummaryViewModel
{
public string Name { get; set; }
public static Expression<Func<Data.Blog, BlogSummaryViewModel>> Map()
{
return (i => new BlogSummaryViewModel
{
Name = i.Name
});
}
}
public class BlogViewModel : BlogSummaryViewModel
{
public int PostCount { get; set; }
public static Expression<Func<Data.Blog, BlogViewModel>> Map()
{
return (i => new BlogViewModel
{
Name = i.Name,
PostCount = i.Posts.Count()
});
}
}
I adapted the solution provided by #Marc Gravell like so:
public static class ExpressionMapExtensions
{
public static Expression<Func<TSource, TTargetB>> Concat<TSource, TTargetA, TTargetB>(
this Expression<Func<TSource, TTargetA>> mapA, Expression<Func<TSource, TTargetB>> mapB)
where TTargetB : TTargetA
{
var param = Expression.Parameter(typeof(TSource), "i");
return Expression.Lambda<Func<TSource, TTargetB>>(
Expression.MemberInit(
((MemberInitExpression)mapB.Body).NewExpression,
(new LambdaExpression[] { mapA, mapB }).SelectMany(e =>
{
var bindings = ((MemberInitExpression)e.Body).Bindings.OfType<MemberAssignment>();
return bindings.Select(b =>
{
var paramReplacedExp = new ParameterReplaceVisitor(e.Parameters[0], param).VisitAndConvert(b.Expression, "Combine");
return Expression.Bind(b.Member, paramReplacedExp);
});
})),
param);
}
private class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression original;
private readonly ParameterExpression updated;
public ParameterReplaceVisitor(ParameterExpression original, ParameterExpression updated)
{
this.original = original;
this.updated = updated;
}
protected override Expression VisitParameter(ParameterExpression node) => node == original ? updated : base.VisitParameter(node);
}
}
The Map method of the extended class then becomes:
public static Expression<Func<Data.Blog, BlogViewModel>> Map()
{
return BlogSummaryViewModel.Map().Concat(i => new BlogViewModel
{
PostCount = i.Posts.Count()
});
}

Categories

Resources