gpt4 book ai didi

c# - 如何映射两个不同类型的表达式?

转载 作者:行者123 更新时间:2023-11-30 19:11:30 25 4
gpt4 key购买 nike

我将从一些类(class)开始......

域实体:

public class Account
{
public int Id { get; set; }
public double Balance { get; set; }
public string CustomerName { get; set; }
}

View 模型:

public class AccountModel
{
public int Id { get; set; }
public double Bal { get; set; }
public string Name { get; set; }
}

存储库:

我的存储库上有一个方法,它接受一个表达式并返回一个列表,如下所示:

public interface IAccountRepository
{
IEnumerable<Account> Query(Expression<Func<Account, bool>> expression);
}

问题

我的应用程序生成一个 Expression<Func<AccountModel, bool>>在用户界面中。我需要以某种方式转换或映射 AccountModel 中的 EXPRESSIONAccount这样我就可以在我的 Query 中使用它方法。我说“ map ”是因为,如果您注意到的话,我的模型和域对象很相似,但不一定具有相同的属性名称。

如何做到这一点?

最佳答案

这听起来像是 AutoMapper 的工作. Automapper 允许您在一个时间点将一个类映射到另一个类,并在以后使用此映射配置。

参见 Projection在 wiki 上找到您想要的东西的页面。

更新 当您使用 Entity Framework 时,这里有一个更新,用于将您的表达式从使用 AccountModel 重新映射到 Account

CompositionRoot在您的应用程序中,像这样设置 AutoMapper(如果您不使用代码契约(Contract),请忽略代码契约(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 将成员访问从一种类型重新绑定(bind)到另一种类型。

/// <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>();

关于c# - 如何映射两个不同类型的表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11248585/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com