假设我使用以下 Entity Framework 实体:
public class Country : DomainObject<int>
{
private ICollection<StateOrProvince> _statesOrProvinces;
public string Name { get; set; }
public string Abbreviation { get; set; }
public virtual ICollection<StateOrProvince> StatesOrProvinces
{
get { return _statesOrProvinces ?? (_statesOrProvinces = new List<StateOrProvince>()); }
protected set { _statesOrProvinces = value; }
}
}
public class StateOrProvince : DomainObject<int>
{
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
我想为我的表示层投影到以下 View 模型:
public class CountryListModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
[Display(Name = "States/Provinces")]
public int StatesOrProvincesCount { get; set; }
}
如您所见,我的模型上有一个 StateOrProvincesCount 属性,该属性应该表示我的国家/地区实体上 StatesOrProvinces 列表的聚合计数。
为了保持 MVC Controller 代码精简,我为域实体创建了静态映射扩展方法。用于映射到 CountryListModel 的模型如下所示:
public static CountryListModel MapToListModel(this Country country)
{
return new CountryListModel
{
Id = country.Id,
Name = country.Name,
Abbreviation = country.Abbreviation,
StatesOrProvincesCount = country.StatesOrProvinces.Count
};
}
然后我尝试按如下方式使用它:
var models = _countries.OrderBy(x => x.Name).Select(x => x.MapToListModel()).ToList();
但是,这引发了异常LINQ to Entities 无法识别“CountryListModel MapToListModel(Country)”方法,并且此方法无法转换为存储表达式。 我的假设是这是IQueryable
存在无法将代码转换为 SQL 的问题。
然后我尝试了:
var models = _countries.AsEnumerable().OrderBy(x => x.Name).Select(x => x.MapToListModel()).ToList();
这并没有引发错误,并给了我想要的结果。但是,当查看 SQL Express Profiler 时,我可以看到这会导致 (N+1) 查询被发送到数据库。首先,它查询以获取国家/地区列表,然后查询以选择每个国家/地区的所有州/省。
如果您直接在 .Select
方法中丢弃映射扩展和项目,则只有一个查询,并且它直接在 SQL 中执行 Count
而不是返回然后计算州/省:
var models = _countries.OrderBy(x => x.Name).Select(x => new CountryListModel
{
Id = x.Id,
Name = x.Name,
Abbreviation = x.Abbreviation,
StatesOrProvincesCount = x.StatesOrProvinces.Count
}).ToList();
这太棒了,但这只是一个快速原型(prototype)。从长远来看,我想将事物分成不同的层(例如 - 核心、数据、业务、演示)。
从我在此过程中学到的知识来看,数据层必须知道我正在投影的 View 模型才能有效地查询。在这种情况下, View 模型是否与域实体一起属于核心/公共(public)项目?我应该创建额外的 DTO 对象并映射到这些对象上吗?您在项目中如何处理这个问题?
最佳答案
我不认为你的解决方案是错误的。我以前用过它,有时最终会编写自己的 SQL 并传递给 EF。有时,这比尝试调整 EF 来创建您想要的要容易得多。在这种情况下,EF 对您的扩展方法根本无能为力。对于评论中提到的解决方案,由于扩展方法中的 country.StatesOrProvinces.Count
,您将遇到 N+1。如果您不能将其扁平化,那么编写 SQL 可能是一个不错的选择。
In the long run I want to separate things into layers (e.g. - Core, Data, Business, Presentation).
我在过去的项目中曾多次尝试实现这一目标,使用了各种存储库、UnitOfWorks、ViewModels等。结果不太令人满意。它常常导致数据访问层和服务层臃肿,到处违反单一责任原则,增加了更多的维护麻烦。这个post罗布·康纳利 (Rob Conery) 的著作让我有了不同的想法。保持简单比你不需要的教条优化更重要。两者都讨论了解决方案,命令/查询对象(不一定是完整的 CQRS)和在请求的生命周期中使用 DataContext 效果很好,并且随着项目的生命周期而良好地成长。
In cases such as this do the View Models belong in the Core/Common project along with the Domain entities? Should I create additional DTO objects and map onto those? How do you handle this in your projects?
我强烈建议您看看 Jimmy Bogard 对 Contoso University App 的看法。 。他正在使用MediatoR 。它解耦得很好。值得一试。
没有 Elixir 。在开始收紧代码之前,我要记住的事情是简单性、可测试性和性能。总是存在将“通用代码”放在可由多个事物访问的某个地方的诱惑。在创建此类实体之前,我会考虑更改的原因。
我希望这会有所帮助。
关于c# - 将域模型子计数投影到 View 模型上,无需额外查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29637516/