当属性具有相同代码时保持 AutoMapper ProjectTo() 干燥
Keeping AutoMapper ProjectTo() DRY When Properties Have the Same Code
我想使用 AutoMapper 的可查询扩展 (.ProjectTo
),但如果不复制数据库对象的许多属性中的代码并将其复制到投影中,我不知道该怎么做.下面是一个既覆盖 ToString 又具有自定义 属性 的示例:
class Claim {
public int Id { get; set; }
public int TypeId { get; set; }
public ClaimType Type { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Name
=> $"{FirstName} {LastName}";
}
class ClaimType {
public int Id { get; set; }
public string Value { get; set; }
public string Abbrev { get; set; }
public override string ToString()
=> Value + (Abbrev != null ? $" ({Abbrev})" : "");
}
class ClaimViewModel {
public int Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
}
现在假设我有上述两个数据库模型和视图模型,我想在查询中将 Claim 投影到 ClaimViewModel。我知道如何做到这一点的唯一方法是这样的:
CreateMap<Claim, ClaimViewModel>()
.ForMember(c => c.ClaimType, x => x.MapFrom(c => c.ClaimType.Value + (c.ClaimType.Abbrev != null ? $" ({c.ClaimType.Abbrev})" : "")))
.ForMember(c => c.Name, x => x.MapFrom(c => $"{c.FirstName} {c.LastName}"));
显然,这重复了名称 属性 和 ClaimType.ToString 方法的代码。是否有解决此问题并保持代码 DRY 的既定模式?我们的模型中有很多自定义属性和 ToString 覆盖。我发现我可以为姓名 属性 执行以下操作...首先,在声明 class 中:
public static readonly Expression<Func<Claim, string>> NameExpr = (c) => $"{c.FirstName} {c.LastName}";
public string Name => NameExpr(this);
然后在映射中执行此操作:
.ForMember(c => c.Name, x => x.MapFrom(Claim.NameExpr));
这对于非常简单的情况似乎没问题,但它不适用于具有外键引用的情况,例如 ClaimType.ToString 方法。在这种情况下,我可以将表达式放在 Claim 中并在 Claim 映射中引用它,但是当我需要将 ClaimType 投影到 ClaimTypeViewModel 时,我必须复制代码。或者,如果有另一个数据库模型引用了 ClaimType 模型,我也会遇到同样的问题。
如果对答案很重要,ORM 是 EF Core @ 2.1.3。
编辑: 我在写这个问题时没有意识到,但是上面的映射在没有 ForMember 配置的情况下也可以工作,但我在 SQL Profiler 中注意到查询将拉回 Claim 和 ClaimType 模型中的每一列,而不仅仅是所需的列。这很好,但我真的需要 ProjectTo 的性能奖励,只提取实际需要的列。
@LucianBargaoanu 提供了一个伟大的、有效的解决方案的正确关键。执行实际工作的 AutoMapper.EF6 provides some simple wrappers to do this; what they wrap is actually the DelegateDecompiler 包。最终解决方案如下:
首先,通过将 NuGet 包添加到您的项目来引用 DelegateDecompiler。
其次,将 ComputedAttribute
添加到您希望 EFCore 能够正确处理变成 SQL 的模型上的任何计算属性或函数,例如:
class Claim {
[Computed]
public string Name
=> $"{FirstName} {LastName}";
}
class ClaimType {
[Computed]
public override string ToString()
=> Value + (Abbrev != null ? $" ({Abbrev})" : "");
}
最后,任何你想调用ProjectTo
的地方,也附加Decompile()
(或DecompileAsync()
)。例如
var myClaimViewModels = Db.Claims.ProjectTo<ClaimViewModel>(Mapper.ConfigurationProvider).Decompile().ToList();
我验证了上面的代码工作得很好,即使使用标记为 Computed
的重写 ToString
方法也是如此。我检查了 SQL Server Profiler 以确保查询只拉回必要的列,谢天谢地,它是。
作为参考,AutoMapper.EF6 库实际上只有一个 single source file,它可以很容易地复制并粘贴到您自己的源代码中,目标是 EF Core 而不是 EF 6。
我想使用 AutoMapper 的可查询扩展 (.ProjectTo
),但如果不复制数据库对象的许多属性中的代码并将其复制到投影中,我不知道该怎么做.下面是一个既覆盖 ToString 又具有自定义 属性 的示例:
class Claim {
public int Id { get; set; }
public int TypeId { get; set; }
public ClaimType Type { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Name
=> $"{FirstName} {LastName}";
}
class ClaimType {
public int Id { get; set; }
public string Value { get; set; }
public string Abbrev { get; set; }
public override string ToString()
=> Value + (Abbrev != null ? $" ({Abbrev})" : "");
}
class ClaimViewModel {
public int Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
}
现在假设我有上述两个数据库模型和视图模型,我想在查询中将 Claim 投影到 ClaimViewModel。我知道如何做到这一点的唯一方法是这样的:
CreateMap<Claim, ClaimViewModel>()
.ForMember(c => c.ClaimType, x => x.MapFrom(c => c.ClaimType.Value + (c.ClaimType.Abbrev != null ? $" ({c.ClaimType.Abbrev})" : "")))
.ForMember(c => c.Name, x => x.MapFrom(c => $"{c.FirstName} {c.LastName}"));
显然,这重复了名称 属性 和 ClaimType.ToString 方法的代码。是否有解决此问题并保持代码 DRY 的既定模式?我们的模型中有很多自定义属性和 ToString 覆盖。我发现我可以为姓名 属性 执行以下操作...首先,在声明 class 中:
public static readonly Expression<Func<Claim, string>> NameExpr = (c) => $"{c.FirstName} {c.LastName}";
public string Name => NameExpr(this);
然后在映射中执行此操作:
.ForMember(c => c.Name, x => x.MapFrom(Claim.NameExpr));
这对于非常简单的情况似乎没问题,但它不适用于具有外键引用的情况,例如 ClaimType.ToString 方法。在这种情况下,我可以将表达式放在 Claim 中并在 Claim 映射中引用它,但是当我需要将 ClaimType 投影到 ClaimTypeViewModel 时,我必须复制代码。或者,如果有另一个数据库模型引用了 ClaimType 模型,我也会遇到同样的问题。
如果对答案很重要,ORM 是 EF Core @ 2.1.3。
编辑: 我在写这个问题时没有意识到,但是上面的映射在没有 ForMember 配置的情况下也可以工作,但我在 SQL Profiler 中注意到查询将拉回 Claim 和 ClaimType 模型中的每一列,而不仅仅是所需的列。这很好,但我真的需要 ProjectTo 的性能奖励,只提取实际需要的列。
@LucianBargaoanu 提供了一个伟大的、有效的解决方案的正确关键。执行实际工作的 AutoMapper.EF6 provides some simple wrappers to do this; what they wrap is actually the DelegateDecompiler 包。最终解决方案如下:
首先,通过将 NuGet 包添加到您的项目来引用 DelegateDecompiler。
其次,将 ComputedAttribute
添加到您希望 EFCore 能够正确处理变成 SQL 的模型上的任何计算属性或函数,例如:
class Claim {
[Computed]
public string Name
=> $"{FirstName} {LastName}";
}
class ClaimType {
[Computed]
public override string ToString()
=> Value + (Abbrev != null ? $" ({Abbrev})" : "");
}
最后,任何你想调用ProjectTo
的地方,也附加Decompile()
(或DecompileAsync()
)。例如
var myClaimViewModels = Db.Claims.ProjectTo<ClaimViewModel>(Mapper.ConfigurationProvider).Decompile().ToList();
我验证了上面的代码工作得很好,即使使用标记为 Computed
的重写 ToString
方法也是如此。我检查了 SQL Server Profiler 以确保查询只拉回必要的列,谢天谢地,它是。
作为参考,AutoMapper.EF6 库实际上只有一个 single source file,它可以很容易地复制并粘贴到您自己的源代码中,目标是 EF Core 而不是 EF 6。