3

いくつかのクラスから始めましょう...

ドメインエンティティ:

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 interface IAccountRepository
{
    IEnumerable<Account> Query(Expression<Func<Account, bool>> expression);
} 

問題

私のアプリケーションExpression<Func<AccountModel, bool>>はUIでを生成します。メソッドで使用できるように、 EXPRESSIONをからAccountModelに変換またはマップする必要があります。「マップ」と言うのは、お気づきの方もいらっしゃると思いますが、私のモデルオブジェクトとドメインオブジェクトは似ていますが、必ずしも同じプロパティ名を持っているとは限りません。AccountQuery

これはどのように行うことができますか?

4

2 に答える 2

9

これはAutoMapperの仕事のように聞こえます。Automapperを使用すると、ある時点で1つのクラスを別のクラスにマップし、後でこのマッピング構成を使用できます。

あなたが求めているものの種類については、ウィキのプロジェクションページを参照してください。

更新EntityFrameworkを使用しているので、式を使用AccountModelからに再マッピングするための更新がありますAccount

アプリケーションのCompositionRootで、AutoMapperを次のように設定します(コードコントラクトを使用しない場合は、コードコントラクトステートメントを無視してください)。

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

これにより、2つのデータ型が相互にどのように関連するかが構成されます。

ExpressionVisitorAutoMapperを使用して、メンバーアクセスをあるタイプから別のタイプに再バインドするを実装します。

/// <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>();
于 2012-06-28T16:13:07.177 に答える
2

を使用して:ExpressionVisitorを書き直すことができます。Expression

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;
    }

}

このビジターは、入力パラメーターをタイプAccountModelからAccountVisitLambdaおよびVisitParameterメソッド)に切り替え、すべてのプロパティアクセサーをこの新しいパラメーターを使用するように変更し、必要に応じてプロパティ名を切り替えます(これがそのVisitMember一部です)。

使用法は次のとおりです。

Expression<Func<AccountModel, bool>> accountModelQuery = a => a.Bal == 0 && a.Name != null && a.Id != 7;

var accountQuery = (Expression<Func<Account, bool>>)new AccountModelRewriter().Visit(accountModelQuery);
于 2012-06-28T16:07:08.477 に答える