这听起来像是AutoMapper的工作。Automapper 允许您在某个时间点将一个类映射到另一个类,并在以后使用此映射配置。
请参阅 wiki 上的投影页面,了解您所追求的东西。
更新当您使用实体框架时,这里有一个更新,用于将您的表达式从 using 重新映射AccountModel
到Account
。
在您的应用程序的CompositionRoot中,像这样设置 AutoMapper(如果您不使用 Code Contracts,请忽略 Code Contract 语句):
var accountModelMap = Mapper.CreateMap<AccountModel, Account>();
Contract.Assume(accountModelMap != null);
accountModelMap.ForMember(account => account.Id, expression => expression.MapFrom(model => model.Id));
accountModelMap.ForMember(account => account.Balance, expression => expression.MapFrom(model => model.Bal));
accountModelMap.ForMember(account => account.CustomerName, expression => expression.MapFrom(model => model.Name));
这配置了两种数据类型如何相互关联。
实现一个ExpressionVisitor
以使用 AutoMapper 将成员访问从一种类型重新绑定到另一种类型。
/// <summary>
/// An <see cref="ExpressionVisitor"/> implementation which uses <see href="http://automapper.org">AutoMapper</see> to remap property access from elements of type <typeparamref name="TSource"/> to elements of type <typeparamref name="TDestination"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor
{
private readonly ParameterExpression _newParameter;
private readonly TypeMap _typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
/// <summary>
/// Initialises a new instance of the <see cref="AutoMapVisitor{TSource, TDestination}"/> class.
/// </summary>
/// <param name="newParameter">The new <see cref="ParameterExpression"/> to access.</param>
public AutoMapVisitor(ParameterExpression newParameter)
{
Contract.Requires(newParameter != null);
_newParameter = newParameter;
Contract.Assume(_typeMap != null);
}
[ContractInvariantMethod]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
private void ObjectInvariant()
{
Contract.Invariant(_typeMap != null);
Contract.Invariant(_newParameter != null);
}
/// <summary>
/// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression"/>.
/// </summary>
/// <returns>
/// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
/// </returns>
/// <param name="node">The expression to visit.</param>
protected override Expression VisitMember(MemberExpression node)
{
var propertyMaps = _typeMap.GetPropertyMaps();
Contract.Assume(propertyMaps != null);
// Find any mapping for this member
var propertyMap = propertyMaps.SingleOrDefault(map => map.SourceMember == node.Member);
if (propertyMap == null)
return base.VisitMember(node);
var destinationProperty = propertyMap.DestinationProperty;
Contract.Assume(destinationProperty != null);
var destinationMember = destinationProperty.MemberInfo;
Contract.Assume(destinationMember != null);
// Check the new member is a property too
var property = destinationMember as PropertyInfo;
if (property == null)
return base.VisitMember(node);
// Access the new property
var newPropertyAccess = Expression.Property(_newParameter, property);
return base.VisitMember(newPropertyAccess);
}
}
然后实现一个扩展方法以使其更易于使用:
/// <summary>
/// A class which contains extension methods for <see cref="Expression"/> and <see cref="Expression{TDelegate}"/> instances.
/// </summary>
public static class ExpressionExtensions
{
/// <summary>
/// Remaps all property access from type <typeparamref name="TSource"/> to <typeparamref name="TDestination"/> in <paramref name="expression"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
/// <typeparam name="TResult">The type of the result from the lambda expression.</typeparam>
/// <param name="expression">The <see cref="Expression{TDelegate}"/> to remap the property access in.</param>
/// <returns>An <see cref="Expression{TDelegate}"/> equivalent to <paramref name="expression"/>, but applying to elements of type <typeparamref name="TDestination"/> instead of <typeparamref name="TSource"/>.</returns>
public static Expression<Func<TDestination, TResult>> RemapForType<TSource, TDestination, TResult>(this Expression<Func<TSource, TResult>> expression)
{
Contract.Requires(expression != null);
Contract.Ensures(Contract.Result<Expression<Func<TDestination, TResult>>>() != null);
var newParameter = Expression.Parameter(typeof (TDestination));
Contract.Assume(newParameter != null);
var visitor = new AutoMapVisitor<TSource, TDestination>(newParameter);
var remappedBody = visitor.Visit(expression.Body);
if (remappedBody == null)
throw new InvalidOperationException("Unable to remap expression");
return Expression.Lambda<Func<TDestination, TResult>>(remappedBody, newParameter);
}
}
这随后可以像这样使用(在 NUnit 测试中):
[TestFixture]
public class RemappingTests
{
#region Setup/Teardown
/// <summary>
/// Sets up the variables before each test.
/// </summary>
[SetUp]
public void Setup()
{
var accountModelMap = Mapper.CreateMap<AccountModel, Account>();
Contract.Assume(accountModelMap != null);
accountModelMap.ForMember(account => account.Id, expression => expression.MapFrom(model => model.Id));
accountModelMap.ForMember(account => account.Balance, expression => expression.MapFrom(model => model.Bal));
accountModelMap.ForMember(account => account.CustomerName, expression => expression.MapFrom(model => model.Name));
}
[TearDown]
public void Teardown()
{
Mapper.Reset();
}
#endregion
/// <summary>
/// Checks that <see cref="ExpressionExtensions.RemapForType{TSource, TDestination, TResult}(Expression{Func{TSource, TResult}})"/> correctly remaps all property access for the new type.
/// </summary>
/// <param name="balance">The balance to use as the value for <see cref="Account.Balance"/>.</param>
/// <returns>Whether the <see cref="Account.Balance"/> was greater than 50.</returns>
[TestCase(0, Result = false)]
[TestCase(80, Result = true)]
public bool RemapperUsesPropertiesOfNewDataType(double balance)
{
Expression<Func<AccountModel, bool>> modelExpr = model => model.Bal > 50;
var accountExpr = modelExpr.RemapForType<AccountModel, Account, bool>();
var compiled = accountExpr.Compile();
Contract.Assume(compiled != null);
var hasBalance = compiled(new Account {Balance = balance});
return hasBalance;
}
}
如果代码太多而无法找到确切的调用,这里是:
Expression<Func<AccountModel, bool>> modelExpr = model => model.Bal > 50;
var accountExpr = modelExpr.RemapForType<AccountModel, Account, bool>();