Automapper - 重用映射规则进行投影
Automapper - reuse of mapping rules for projection
给定 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:
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 映射:
- SourceA => Dest
- SourceB => 目的地
关于如何将 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 的映射规则,因为它们与第一种情况相同?
UPDATE 在我的特定情况下,我想出了如何以自然的方式重用 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 的正确答案。
使用表达式参数化映射:
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 to 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();
}
}
一个快速简单的解决方案是使用中间值 class。
首先使用的 classes 稍后在发布中使用
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; }
}
这里说的是中级class:
public class Intermediate
{
public SourceA SourceA { get; set; } = new SourceA();
public SourceB SourceB { get; set; } = new SourceB();
}
现在让我们开始使用 Automapper 将零件粘在一起。
正在定义配置文件 classes
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.
}
}
下面是我们在上面标记为待办事项的映射的繁重工作。
您可以使用 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>());
最后我们将我们的配置文件 classes 注册到 Automapper
public static class AutomapperProfile
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<DestinationProfile>();
cfg.AddProfile<IntermediateProfile>();
});
}
}
使用以下代码片段启动控制台有助于测试我们的东西
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();
完成!
注意: 提供的代码片段只是为了演示 usage/meaning "intermediate" class 的含义而编码 - 还没有实现了返回源头的方式 classes.
玩得开心 :)
给定 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:
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 映射:
- SourceA => Dest
- SourceB => 目的地
关于如何将 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 的映射规则,因为它们与第一种情况相同?
UPDATE 在我的特定情况下,我想出了如何以自然的方式重用 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 的正确答案。
使用表达式参数化映射:
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 to 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();
}
}
一个快速简单的解决方案是使用中间值 class。
首先使用的 classes 稍后在发布中使用
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; }
}
这里说的是中级class:
public class Intermediate
{
public SourceA SourceA { get; set; } = new SourceA();
public SourceB SourceB { get; set; } = new SourceB();
}
现在让我们开始使用 Automapper 将零件粘在一起。
正在定义配置文件 classes
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.
}
}
下面是我们在上面标记为待办事项的映射的繁重工作。 您可以使用 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>());
最后我们将我们的配置文件 classes 注册到 Automapper
public static class AutomapperProfile
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<DestinationProfile>();
cfg.AddProfile<IntermediateProfile>();
});
}
}
使用以下代码片段启动控制台有助于测试我们的东西
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();
完成!
注意: 提供的代码片段只是为了演示 usage/meaning "intermediate" class 的含义而编码 - 还没有实现了返回源头的方式 classes.
玩得开心 :)