2012-07-12 84 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