我想使用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方法的代码。是否有解决此问题并使代码保持DRY的已建立模式?我们的模型中有很多自定义属性和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的性能红利,只需拉出实际需要的列即可。
答案 0 :(得分:0)
@LucianBargaoanu提供了一个不错的可行解决方案的正确钥匙。 AutoMapper.EF6提供了一些简单的包装器来完成此操作;它们包装的实际上是完成实际工作的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();
即使使用标记为ToString
的覆盖的Computed
方法,我也验证了上面的代码能很好地工作。我检查了SQL Server Profiler,以确保查询仅拉回必要的列,幸运的是,它是。
作为参考,AutoMapper.EF6库实际上只有一个single source file,可以轻松地将其复制并粘贴到您自己的源中,以EF Core为目标,而不是EF 6。