2015-04-14 14 views
2

假設我使用下面的實體框架的實體:投影領域模型的孩子數到視圖模型沒有額外查詢

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到實體不承認方法'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(); 

這太棒了,但這只是一個快速的原型。從長遠來看,我希望將事情分成不同層次(例如 - 核心,數據,商業,演示)。

從我在這個過程中學到的知識來看,爲了有效地查詢,我所預測的視圖模型必須被數據層所瞭解。在這種情況下,視圖模型是否屬於核心/公共項目以及域實體?我應該創建額外的DTO對象並映射到這些對象上嗎?你如何在你的項目中處理這個問題?

+2

'MapToListModel()'在SQL Server中不存在,所以它不知道你想要什麼。您是否嘗試過:'_countries.OrderBy(x => x.Name).ToList()。Select(x => x.MapToListModel())。ToList();'你也在問一些關於編程的概念問題,更適合http://programmers.stackexchange.com關於核心/數據/業務/演示。 –

+2

您可以使用https://automapper.codeplex.com/(AutoMapper)在Domain對象和ViewModels之間進行映射......它只是一個要添加到項目中的配置圖層 –

+0

Erik,可以工作,但會導致* * N + 1 **查詢就像我的另一個例子。謝里夫,我知道AutoMapper,但沒有看到它是如何解決聚合** N + 1 **查詢問題。 Linq to SQL不知道有關AutoMapper的更多信息,而不瞭解擴展方法。 – Sam

回答

2

我不認爲你的解決方案是錯誤的。我以前使用過它,有時最終編寫自己的SQL並傳遞給EF。有時候比試圖彎曲EF來創建你想要的更容易。在這種情況下,EF不能對你的擴展方法做很多事情。對於評論中提到的解決方案,由於擴展方法中的country.StatesOrProvinces.Count,您正在擊中N + 1。如果你不能做到這一點,那麼編寫你的SQL可能是一個不錯的選擇。

從長遠來看,我想分離成層(例如, - 核心,數據,業務,演示)。

我曾嘗試在過去的項目中實現過好幾次,使用過各種類型的倉庫,UnitOfWorks,ViewModels等。結果並不令人滿意。它經常導致數據訪問層和服務層臃腫到處違反單一責任原則,增加了更多的維護難題。Rob Conery的post讓我覺得不同。保持簡單比您不需要的教條優化更重要。這兩個討論的解決方案,命令/查詢對象(不一定是完整的CQRS)和使用DataContext在請求的生命週期中工作良好,並且與項目的生命週期一起很好地發展。

在這種情況下,視圖模型是否屬於核心/公共項目以及域實體?我應該創建額外的DTO對象並映射到這些對象上嗎?你如何在你的項目中處理這個問題?

我強烈建議看一下Jimmy Bogard的Contoso University App。他正在使用MediatoR。它非常好地分離。這是值得一試的。

沒有銀彈。在開始收緊代碼之前,我會牢記的是簡單性,可測試性和性能。總有一種將「通用代碼」放在某些地方被幾種東西訪問的誘惑。在創建這樣的實體之前,我會想到一個改變的原因。

我希望這會有所幫助。