2012-07-12 48 views
0

我正在使用ASP.net中的依賴注入開發我的第一個數據驅動域。如何高效地填充數據訪問層模型?

在我的數據訪問層,如果創造了一些領域的數據模型,例如:

public class Company { 
    public Guid CompanyId { get; set; } 
    public string Name { get; set; } 
} 

public class Employee { 
    public Guid EmployeeId { get; set; } 
    public Guid CompanyId { get; set; } 
    public string Name { get; set; } 
} 

我已經再開發的接口,如:

public interface ICompanyService { 
    IEnumerable<Model.Company> GetCompanies(); 
    IEnumerable<Model.Employee> GetEmployees(); 
    IEnumerable<Model.Employee> GetEmployees(Guid companyId); 
} 

在一個單獨的模塊,我已經實現這個接口使用Linq到Sql:

public class CompanyService : ICompanyService { 

    public IEnumerable<Model.Employee> GetEmployees(); 
    { 
     return EmployeeDb 
      .OrderBy(e => e.Name) 
      .Select(e => e.ToDomainEntity()) 
      .AsEnumerable(); 
    } 
} 

WhereDoDomainEntity()被實現在員工信息庫類的擴展方法基礎機構類:

public Model.EmployeeToDomainEntity() 
{ 
    return new Model.Employee { 
     EmployeeId = this.EmployeeId, 
     CompanyId = this.CompanyId, 
     Name = this.Name 
    }; 
} 

對於這一點,我有或多或少的遵循了模式,如馬克西曼的優秀圖書「在.NET依賴注入」中描述 - 和所有的作品都很好。

我卻想延長我的基本款也包括關鍵的參考模型,因此域Employee類將成爲:

public class Employee { 
    public Guid EmployeeId { get; set; } 
    public Guid CompanyId { get; set; } 
    public Company { get; set; } 
    public string Name { get; set; } 
} 

和ToDomainEntity()函數將擴大到:

public Model.Employee ToDomainEntity() 
{ 
    return new Model.Employee { 
     EmployeeId = this.EmployeeId, 
     CompanyId = this.CompanyId, 
     Company = (this.Company == null) ? null : this.Company.ToDomainEntity() 
     Name = this.Name 
    }; 
} 

我懷疑從領域建模的角度來看這可能是'不好的做法',但我認爲,如果我要開發一個特定的視圖模型來達到相同的目的,我也會遇到這個問題。

實質上,我遇到的問題是填充數據模型的速度/效率。如果我使用上述的ToDomainEntity()方法,Linq to Sql會創建一個單獨的SQL調用來檢索每個員工公司記錄的數據。正如您所期望的那樣,這會大大增加評估SQL表達式所花費的時間(從我們的測試數據庫大約100ms到7秒),特別是如果數據樹很複雜(因爲單獨的SQL調用是爲了填充每個節點/樹的子節點)。

如果我創建數據模型「內聯......

public IEnumerable<Model.Employee> GetEmployees(); 
{ 
    return EmployeeDb 
     .OrderBy(e => e.Name) 
     .Select(e => new Model.Employee { 
      EmployeeId = e.EmployeeId, 
      /* Other field mappings */ 
      Company = new Model.Company { 
       CompanyId = e.Company.CompanyId, 
       /* Other field mappings */ 
      } 
     }).AsEnumerable(); 
} 

LINQ to SQL的產生本身使用了一個漂亮的,緊密的SQL語句‘內連接’的方法,以本公司與員工聯繫起來。

我有兩個問題:

1)它認爲「不好的做法」從一個域類對象中引用相關數據類?

2)如果是這種情況,並且爲此目的創建了特定的視圖模型,那麼使用填充模型的正確方法是什麼,而不必訴諸創建內聯分配塊來構建表達式樹?

任何幫助/建議將不勝感激。

+0

如果您開始開發,最好選擇實體框架(而不是LINQ to SQL),因爲Microsoft將來會繼續開發實體框架。 – Steven 2012-07-12 10:16:44

+0

一些額外的研究表明,這裏的底層問題是Linq(n + 1)問題 - 在StackOverflow中很好地介紹了這個問題。到目前爲止,我還沒有看到在代碼中解決這個問題的好方法 - 除了創建特定的視圖模型(我希望避免)。我確實發現了一篇文章,建議使用表達式構建可能是答案,但我找不到任何可能如何開發這種方法的例子。 – Neilski 2012-07-12 19:44:10

回答

1

該問題是由同時具有數據層實體和域層實體並且需要兩者之間的映射引起的。儘管你可以使這個工作起來,但是這使得一切都非常複雜,正如你已經經歷的那樣。您正在數據和域之間進行映射,並且由於性能原因以及其他業務邏輯和表示邏輯將需要不同的數據,因此很快將爲這些相同實體添加更多映射。

唯一真正的解決方案是拋棄數據實體並創建可直接序列化到後端存儲(SQL Server)的POCO模型對象。

從第一天起,POCO實體在LINQ to SQL中得到支持,但我認爲遷移到Entity Framework Code First會更好。

當您這樣做時,您可以從您的存儲庫中暴露IQueryable<T>接口(您目前稱爲存儲庫ICompanyService,但更好的名稱是ICompanyRepository)。這使您可以執行高效的LINQ查詢。直接通過查詢提供程序查詢時,可以防止加載完整的實體。例如:

from employee in this.repository.GetEmployees() 
where employee.Company.Name.StartWith(searchString) 
select new 
{ 
    employee.Name, 
    employee.Company.Location 
}; 

IQueryable<T>工作,LINQ to SQL和實體框架會翻譯這一個非常有效的SQL查詢只從數據庫中篩選數據庫返回僱工的姓名和公司位置(相較於在GetEmployees()返回IEnumerable<T>時,在您的.NET應用程序中進行篩選)。

+0

謝謝史蒂芬 - 非常有用。我可以看到,這簡化了模型,它幾乎是我原來的樣子,但我導致相信「更好」的方法是在應用程序級別使用域驅動數據模型和依賴注入。然而,我目前的感覺是,儘管我喜歡它提供的分離和模塊化,但看起來好像還有很多工作要做,並且引入了一些與核心SQL數據庫更直接耦合的問題。我必須承認,我現在對這條路線有了第二個想法,我不確定最佳的前進方向。 – Neilski 2012-07-13 12:16:36

0

您可以使用DataLoadOptions.LoadWith方法請求Linq2Sql預加載某些實體(而不是延遲加載它們),請參閱:http://msdn.microsoft.com/en-us/library/bb534268.aspx。 如果你用Company實體做到這一點,那麼我認爲Linq2Sql將不必到達數據庫再次獲取它。

+0

謝謝帕維爾,我試過這個,但實際上並沒有發現任何讓我驚訝的區別。我認爲我唯一堅實的途徑是從核心數據模型中刪除關係,並創建需要相關數據的特定視圖模型。這似乎工作得很好,儘管它在創建視圖模型和提供填充它們的機制方面引入了相當大的開銷。 – Neilski 2012-07-13 12:20:44

+0

我很確定它確實有所作爲,並在AdventureWorks db中檢查了一個類似的查詢。它在那裏工作得很好。 – 2012-07-22 21:37:22

+0

首先我運行這個查詢: 'var dc = new UserQuery(this.Connection); var query = dc.Products.Take(10); query.Dump();' 它生成了一個沒有任何連接的可預測的SQL語句。但是,當我添加LoadOptions: 'var dc = new UserQuery(this.Connection); var dlo = new DataLoadOptions(); dlo.LoadWith (p => p.ProductSubcategory); dc.LoadOptions = dlo; var query = dc.Products.Take(10); query.Dump();它產生一個SQL語句,其中一個連接到子類別表。 – 2012-07-22 21:43:16