【问题标题】:Automapper - reuse of mapping rules for projectionAutomapper - 重用映射规则进行投影
【发布时间】:2017-05-20 13:05:53
【问题描述】:

给定 2 个源实体:

class SourceA
{
   public string Info1 { get; set; }
   public string Info2 { get; set; }
}

class SourceB
{
   public A A { get; set; }

   public string OptionalExtraInfo { get; set; }
}

和一个目的地类别:

class Dest
{
   public string ModifiedInfo1 { get; set; }
   public string ModifiedInfo2 { get; set; }

   public string ModifiedOptionalExtraInfo { get; set; }
}

我想让以下代码与 EF6 一起使用:

var destsFromA = dbContext.SourcesA.ProjectTo<Dest>().ToArray();
var destsFromB = dbContext.SourcesB.ProjectTo<Dest>().ToArray();

所以我定义了 Automapper.net 映射:

  • 来源A => 目的地
  • 来源B => 目的地

关于如何将 Info1 投影到 ModifiedInfo1 和 Info2=>ModifiedInfo2 的自定义规则:

CreateMap<SourceA, Dest>()
    .ForMember(x => ModifiedInfo1, opt => opt.MapFrom(src => src.Info1 + " something else-1")
    .ForMember(x => ModifiedInfo2, opt => opt.MapFrom(src => src.Info1 + " something else-2")
    .ForMember(x => ModifiedOptionalExtraInfo, opt => opt.Ignore());

CreateMap<SourceB, Dest>()
    .ForMember(x => ModifiedInfo1, opt => opt.MapFrom(src => src.A.Info1 + " something else-1")
    .ForMember(x => ModifiedInfo2, opt => opt.MapFrom(src => src.A.Info2 + " something else-2")
    .ForMember(x => ModifiedOptionalExtraInfo, opt => opt.MapFrom(src => src.OptionalExtraInfo + " something else-3"));

如何在第二次映射中重用 ModifiedInfo1、ModifiedInfo2 的映射规则,因为它们与第一种情况相同?

更新在我的某些情况下,我想出了如何以自然的方式重用 SourceA => Dest 映射。

首先,我添加了一个反向引用(导航属性)SourceA.B,因为这些实体实际上是一对零或一的关系,EF 必须知道这一点。

然后我在我的应用程序代码中更改了聚合根,它变成了:

var destsFromA = dbContext.SourcesA.ProjectTo<Dest>().ToArray();
var destsFromB = dbContext.SourcesB.Select(x => x.A).ProjectTo<Dest>().ToArray();

所以我只需要使用唯一的 SourceA => Dest 映射

最后我改变了映射本身:

CreateMap<SourceA, Dest>()
    .ForMember(x => ModifiedInfo1, opt => opt.MapFrom(src => src.Info1 + " something else-1")
    .ForMember(x => ModifiedInfo2, opt => opt.MapFrom(src => src.Info1 + " something else-2")
    .ForMember(x => ModifiedOptionalExtraInfo, opt => opt.MapFrom(src => src.B ? src.B.OptionalExtraInfo + " something else-3" : null);

由于这是一个问题的解决方案,而不是原始问题的答案,我接受了 Ilya Chumakov 的答案作为正确答案。

【问题讨论】:

  • 拥有一个属性指向两个源类的中间类,然后在这个中间类上定义 dest 映射怎么样?
  • @DimiToulakis 你能提供一个代码 sn-p 吗?
  • 稍后再做 - 目前不在我的笔记本电脑上

标签: c# .net entity-framework automapper projection


【解决方案1】:

使用表达式参数化映射:

opt.MapFrom(expression)

.ForMember(x => x.Foo, expression)

使用 ReSharper 很容易提取这些表达式变量,因此它可能如下所示:

Expression<Func<SourceA, string>> expression = src => src.Info1 + " something else-1";
var func = expression.Compile();

cfg.CreateMap<SourceA, Dest>()
    .ForMember(x => x.ModifiedInfo1, 
        opt => opt.MapFrom(expression));

cfg.CreateMap<SourceB, Dest>()
    .ForMember(x => x.ModifiedInfo1,
        opt => opt.MapFrom(src => func(src.A)));

更新:在 LINQ 到 SQL 转换的情况下,解决方案变得更加复杂。 expression.Compile() 不起作用,应该创建一个新表达式:

Expression<Func<SourceA, string>> expression = src => src.Info1 + "foo";

//it should contain `src => src.A.Info1 + "foo"`
var newExpression = ConvertExpression(expression);

ExpressionVisitor的基本实现:

private static Expression<Func<SourceB, string>> 
    ConvertExpression(Expression<Func<SourceA, string>> expression)
{
    var newParam = Expression.Parameter(typeof(SourceB), "src");

    var newExpression = Expression.Lambda<Func<SourceB, string>>(
        new ReplaceVisitor().Modify(expression.Body, newParam), newParam);

    return newExpression;
}    

class ReplaceVisitor : ExpressionVisitor
{
    private ParameterExpression parameter;

    public Expression Modify(Expression expression, ParameterExpression parameter)
    {
        this.parameter = parameter;

        return Visit(expression);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        return Expression.Lambda<Func<SourceB, bool>>(
            Visit(node.Body), 
            Expression.Parameter(typeof(SourceB)));
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == typeof(SourceA))
        {
            return Expression.Property(parameter, nameof(SourceB.A));
        }

        throw new InvalidOperationException();
    }
}   

【讨论】:

  • 我还没有尝试过,但确定 func() 调用会被正确地翻译成 SQL 吗?因为应该有一种方法可以通过 .ProjectTo() 可查询扩展来使用此映射
  • @AndreyPesoshin 当然func() 不会被翻译成SQL。愚蠢的我。我们需要从原来的表达中创造新的表达。我添加了一个概念验证代码来回答(经过测试)。
  • 是的,看起来重建表达式是在多个映射中重用它的唯一方法。我以另一种方式解决了我的问题(编辑了一个问题),但这种方法对于原始问题是可以接受的。也刚刚有了一个想法 - 我可以通过自动映射器转换表达式本身,因为它提供了这样的能力github.com/AutoMapper/AutoMapper/wiki/… 这会有点hacky,因为我应该在 A 和 B 之间创建一个映射,它不是真正要映射的,但将消除表达式树解析的需要
  • @AndreyPesoshin,谢谢!我之前没有认真对待这个 Automapper 功能。
【解决方案2】:

一个快速简单的解决方案是使用中间类。

先是使用的类,然后在发帖中

public class SourceA 
{
    public string A { get; set; }
}

public class SourceB
{
    public string B { get; set; }
}

public class Dest
{
    public string ValueFromSourceA { get; set; }

    public string ValueFromSourceB { get; set; }
}

这里说的是中间类:

public class Intermediate
{
    public SourceA SourceA { get; set; } = new SourceA();

    public SourceB SourceB { get; set; } = new SourceB();
}

现在让我们开始使用 Automapper 将零件粘贴在一起。

定义配置文件类

public class DestinationProfile : Profile
{
    public DestinationProfile()
    {
        this.CreateMap<Intermediate, Dest>()
            .ForMember(destination => destination.ValueFromSourceA, 
                       opt => opt.MapFrom(src => src.SourceA.A))
            .ForMember(destination => destination.ValueFromSourceB, 
                       opt => opt.MapFrom(src => src.SourceB.B));
    }
}

public class IntermediateProfile : Profile
{
    public IntermediateProfile()
    {
        this.CreateMap<Intermediate, Dest>()
            .ForMember(destination => destination.ValueFromSourceA, map => map.MapFrom(src => src.SourceA.A))
            .ForMember(destination => destination.ValueFromSourceB, map => map.MapFrom(src => src.SourceB.B));

    // ----- TODO: Create mapping for source classes.
    }
}

我们在上面标记为 todo 的映射的繁重工作来了。 您可以使用 Automapper 中的 IValueResolver 接口来定义值映射。 所以在我们的例子中,解析器看起来像

public class SourceAResolver : IValueResolver<SourceA, Intermediate, SourceA>
{
    public SourceA Resolve(SourceA source, Intermediate destination, SourceA destMember, ResolutionContext context)
    {
        return source;
    }
}

public class SourceBResolver : IValueResolver<SourceB, Intermediate, SourceB>
{
    public SourceB Resolve(SourceB source, Intermediate destination, SourceB destMember, ResolutionContext context)
    {
        return source;
    }
}

现在我们可以替换 todo 语句了

        this.CreateMap<SourceA, Intermediate>()
            .ForMember(destination => destination.SourceA, map => map.ResolveUsing<SourceAResolver>());
        this.CreateMap<SourceB, Intermediate>()
            .ForMember(destination => destination.SourceB, map => map.ResolveUsing<SourceBResolver>());

最后我们将配置文件类注册到 Automapper

public static class AutomapperProfile
{
    public static void Configure()
    {
        Mapper.Initialize(cfg =>
        {
            cfg.AddProfile<DestinationProfile>();
            cfg.AddProfile<IntermediateProfile>();
        }); 
    }
}

使用以下代码启动控制台 sn-p 有助于测试我们的东西

        AutomapperProfile.Configure();
        var a = new SourceA {A = "Value A"};

        var b = new SourceB() {B = "Value B"};

        var intermediate = new Intermediate() {SourceA = a, SourceB = b};

        var destination = AutoMapper.Mapper.Map<Dest>(intermediate);

        Console.WriteLine(destination.ValueFromSourceA);

        Console.Read();

完成!

注意:提供的代码 sn-ps 仅用于演示“中间”类的用法/含义 - 尚未实现返回源类的方式。 p>

玩得开心:)

【讨论】:

  • 正如您在上面的问题中看到的那样,这是不可接受的,因为应该通过 IQueryable 投影执行映射而不是 Mapper.Map
  • @andrey 不需要使事情过于复杂:) 提供的示例也适用于使用 IQueryable 本身的 IQueryable。也就是说,假设var mappedItems = new List&lt;Dest&gt;(); var items = new List&lt;Intermediate&gt;(); // ---- create some entries in the items collection items.Where(x =&gt; x.SourceA.A.Contains("1")).ToList().ForEach(x =&gt; mappedItems.Add(Mapper.Map&lt;Dest&gt;(x))); 一切都像一个魅力:)
  • 这并不是关于过度复杂化。当您使用 Mapper.Map 时(如在您的两个示例中) - 映射本身是使用 CPU 执行的,对象存储在 RAM 中准确地说,在第二个示例中,您从 IQueryable 转换为 IEnumerable a.ToList() 被调用,然后执行标准的基于 RAM 的映射。问题的主题是如何通过 .ProjectTo() 自动映射器可查询扩展方法github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions 在 SQL 中执行映射,因此自定义值解析器根本无法在这里工作
猜你喜欢
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 2013-03-11
  • 1970-01-01
相关资源
最近更新 更多