Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/xslt/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何映射两个不同类型的表达式?_C#_Linq - Fatal编程技术网

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

C# 如何映射两个不同类型的表达式?,c#,linq,C#,Linq,我将从一些课程开始 域实体: public class Account { public int Id { get; set; } public double Balance { get; set; } public string CustomerName { get; set; } } 视图模型: public class AccountModel { public int Id { get; set; } public double Bal { ge

我将从一些课程开始

域实体:

public class Account
{
    public int Id { get; set; }
    public double Balance { get; set; }
    public string CustomerName { get; set; }
}
视图模型:

public class AccountModel
{
    public int Id { get; set; }
    public double Bal { get; set; }
    public string Name { get; set; }
}
存储库:

public class Account
{
    public int Id { get; set; }
    public double Balance { get; set; }
    public string CustomerName { get; set; }
}
My repository上有一个方法,它接受一个表达式并返回一个列表,如下所示:

public interface IAccountRepository
{
    IEnumerable<Account> Query(Expression<Func<Account, bool>> expression);
} 
公共接口存储库
{
IEnumerable查询(表达式);
} 
问题

我的应用程序在UI中生成一个
表达式。我需要以某种方式将表达式
AccountModel
转换或映射到
Account
,以便在我的
查询
方法中使用它。我之所以说“map”,是因为,如果您注意到的话,我的模型和域对象是相似的,但不一定有相同的属性名


如何做到这一点?

您可以使用
表达式visitor
重写
表达式

public class AccountModelRewriter : ExpressionVisitor
{

    private Stack<ParameterExpression[]> _LambdaStack = new Stack<ParameterExpression[]>();

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        var lambda = (LambdaExpression)node;

        _LambdaStack.Push(
            lambda.Parameters.Select(parameter => typeof(AccountModel) == parameter.Type ? Expression.Parameter(typeof(Account)) : parameter)
            .ToArray()
        );

        lambda = Expression.Lambda(
            this.Visit(lambda.Body),
            _LambdaStack.Pop()
        );

        return lambda;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        var memberExpression = (MemberExpression)node;

        var declaringType = memberExpression.Member.DeclaringType;
        var propertyName = memberExpression.Member.Name;

        if (typeof(AccountModel) == declaringType)
        {
            switch (propertyName)
            {
                case "Bal" :
                    propertyName = "Balance";
                    break;
                case "Name" :
                    propertyName = "CustomerName";
                    break;
            }

            memberExpression = Expression.Property(
                this.Visit(memberExpression.Expression),
                typeof(Account).GetProperty(propertyName)
            );
        }

        return memberExpression;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        node = (ParameterExpression)base.VisitParameter(node);
        if (typeof(AccountModel) == node.Type)
        {
            node = this._LambdaStack.Peek().Single(parameter => parameter.Type == typeof(Account));
        }
        return node;
    }

}

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

查看wiki上的页面,了解您想要了解的内容

更新当您使用实体框架时,这里有一个更新,用于将表达式从使用
AccountModel
重新映射到
Account

在应用程序中,按如下方式设置AutoMapper(如果不使用代码约定,则忽略代码约定语句):

然后实现一个扩展方法,使其更易于使用:

/// <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);
    }
}
//
///包含和实例的扩展方法的类。
/// 
公共静态类表达式扩展
{
/// 
///将所有属性访问从类型重新映射到中。
/// 
///源元素的类型。
///目标元素的类型。
///lambda表达式的结果类型。
///要在中重新映射属性访问权限,请执行以下操作。
///等效于,但应用于类型为而不是的元素。
公共静态表达式RemapForType(此表达式)
{
Contract.Requires(表达式!=null);
Contract.sure(Contract.Result()!=null);
var newParameter=表达式.参数(typeof(TDestination));
Contract.aspect(newParameter!=null);
var visitor=新的Autompvisitor(新参数);
var remappedBody=visitor.Visit(expression.Body);
if(remappedBody==null)
抛出新的InvalidOperationException(“无法重新映射表达式”);
返回表达式.Lambda(remappedBody,newParameter);
}
}
随后可以这样使用(在NUnit测试中):

[TestFixture]
公开课重映
{
#区域设置/拆卸
/// 
///在每次测试之前设置变量。
/// 
[设置]
公共作废设置()
{
var accountModelMap=Mapper.CreateMap();
Contract.aspect(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.FormMember(account=>account.CustomerName,expression=>expression.MapFrom(model=>model.Name));
}
[撕裂]
公共无效拆卸()
{
Reset();
}
#端区
/// 
///检查是否正确重新映射新类型的所有属性访问权限。
/// 
///要用作的值的余额。
///是否大于50。
[测试用例(0,结果=false)]
[测试用例(80,结果=真)]
public bool重新映射新数据类型的属性(双平衡)
{
表达式modelExpr=model=>model.Bal>50;
var accountExpr=modelExpr.RemapForType();
var compiled=accountExpr.Compile();
Contract.aspect(已编译!=null);
var hasBalance=已编译(新帐户{余额=余额});
返回平衡;
}
}
如果代码太多,无法找到准确的调用,请参见:

Expression<Func<AccountModel, bool>> modelExpr = model => model.Bal > 50;
var accountExpr = modelExpr.RemapForType<AccountModel, Account, bool>();
expressionmodelexpr=model=>model.Bal>50;
var accountExpr=modelExpr.RemapForType();

表达式表示什么类型的过滤器?过滤的实现不应该是UI的关注点吗?我同意@AakashM的观点,UI不应该像这样直接访问。如果它应该调用能够处理的服务,请检查并随后使用表达式。我假设您正在进行某种形式的动态数据访问,因此UI处理
Expression
s?我需要的是一种将
Expression
映射到
Expression
@ByronCommardahl的方法噢,对不起。我已经用一种方法修改了我的答案,以满足您的要求。转换后的表达式是否应该与.Where()ext方法中的EntityFramework一起工作?@ByronCommardahl我认为它不会工作;实体框架不理解如何编译或使用编译后的表达式。@ByronCommardahl@Lukazoid是的,前面的示例不适用于实体框架(我不知道这是问题的要求)。但是,我上面的编辑使用
ExpressionVisitor
重写查询。如果你有很多这样的情况,这些略有不同,那么AutoMapper可能是最好的选择。如果这是一次性的,或者您不能或不想使用第三方库,那么我的示例可能是最好的方法。如果没有对映射进行类型转换(在示例中:int-to-int、double-to-double和string-to-string),这种方法很好用。但是如果有一个从int到int的映射?(可为null的int)类似于accountModelMap.FormMember(account=>account.Id,expression=>expression.MapFrom(model=>model.Id.HasValue?model.Id.Value:0))它将不起作用。知道怎么修吗?谢谢
/// <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);
    }
}
[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>();