将域模型子项计划到视图模型上而无需额外查询

时间:2015-04-14 21:13:55

标签: c# asp.net-mvc 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; }
}

我想投影到我的表示层的以下视图模型:

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控制器代码精益,我为我的域实体创建了静态映射扩展方法。映射到 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,而不是返回States / Provinces,然后计算:

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();

这太棒了,但这只是一个快速的原型。从长远来看,我想将事物分成几层(例如 - 核心,数据,商业,演示)。

从我在此过程中学到的内容看来,我正在投影的视图模型必须由数据层知道才能有效查询。在这种情况下,View Models是否与Domain实体一起属于Core / Common项目?我应该创建其他DTO对象并映射到那些吗?你如何在你的项目中处理这个问题?

1 个答案:

答案 0 :(得分:2)

我认为你的解决方案不对。我以前使用它,有时最终编写自己的SQL并传递给EF。有时比尝试使用EF来创建所需的EF更容易。在这种情况下,EF对您的扩展方法无能为力。对于评论中提到的解决方案,由于扩展方法中的country.StatesOrProvinces.Count,您将达到N + 1。如果你不能弄平,那么编写SQL可能是个不错的选择。

  

从长远来看,我想将事物分成几层(例如 - 核心,数据,商业,演示)。

我曾尝试在以前的项目中多次实现这一点,使用各种存储库,UnitOfWorks,ViewModels等。结果不太令人满意。它经常导致臃肿的数据访问层和服务层在任何地方违反单一责任原则,增加了更多的维护难题.Rob Conery的这个post使我思考的方式不同。保持简单比你不需要的教条优化更重要。两者都讨论了解决方案,命令/查询对象(不一定是完整的CQRS)和使用DataContext在请求的生命周期中运作良好,并与项目的生命周期良好地发展。

  

在这种情况下,View Models是否与Domain实体一起属于Core / Common项目?我应该创建其他DTO对象并映射到那些吗?你如何在你的项目中处理这个问题?

我强烈建议你看看Jimmy Bogard对Contoso University App的看法。他正在使用MediatoR。它很好地解耦了。值得一试。

没有银弹。在开始收紧代码之前,我要记住的是简单性,可测试性和性能。总有一种诱惑是将“公共代码”放在某些地方,这些地方可以通过几种方式访问​​。在创建这样的实体之前,我想到了改变的原因。

我希望这会有所帮助。