c# - 当属性具有相同代码时保持 AutoMapper ProjectTo() DRY
问题描述
我想使用 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}"));
显然,这复制了 Name 属性和 ClaimType.ToString 方法的代码。是否有任何既定的模式可以解决这个问题并保持代码干燥?我们的模型中有很多自定义属性和 ToString 覆盖。我发现我可以对 Name 属性执行以下操作...首先,在 Claim 类中:
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提供了一些简单的包装器来做到这一点;他们包装的实际上是真正工作的DelegateDecompiler包。最终解决方案如下:
首先,通过将 NuGet 包添加到您的项目来引用 DelegateDecompiler。
其次,将ComputedAttribute
EFCore 添加到您希望 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();
我验证了上面的代码运行良好,即使使用ToString
标记为Computed
. 我检查了 SQL Server Profiler 以确保查询只拉回必要的列,谢天谢地,确实如此。
作为参考,AutoMapper.EF6 库实际上只有一个源文件,它可以很容易地复制并粘贴到您自己的源中,目标是 EF Core 而不是 EF 6。
推荐阅读
- bazel - 将 Bazel 规则输出的目录展开为另一个规则的平面输出
- containers - Podman: ERRO[0000] 必须在 /etc/containers/storage.conf 中设置存储“驱动程序”选项
- javascript - 如何对 HTML 表格进行分页?
- c# - Azure Devops 自定义网格字段项目模板
- reactjs - 如何使用 ReactJS 组件添加用户身份验证
- purescript - 从 PureScript Flame 应用程序接收信号
- ios - iOS ARKit 打破常规相机质量
- javascript - 什么是正确的字符串 Json 转换
- c++ - 单击键盘将创建 3-7 个字符
- azure-cosmosdb - Cosmos 客户端和记录版本控制