如何有效地填充数据访问层模型?

时间:2012-07-12 10:12:16

标签: linq-to-sql dependency-injection

我正在使用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 to Sql实现了这个接口:

public class CompanyService : ICompanyService {

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

ToDomainEntity()在员工存储库类中实现为基本实体类的扩展方法:

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

到目前为止,我或多或少地遵循了Mark Seeman的优秀书籍“.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
     };
}

我怀疑从领域建模的角度来看这可能是“不良做法”,但我认为,如果我要开发一个特定的View Model来达到同样的目的,我遇到的问题也会成立。

从本质上讲,我遇到的问题是填充数据模型的速度/效率。如果我使用上述ToDomainEntity()方法,Linq to Sql会创建一个单独的SQL调用来检索每个Employee的Company记录的数据。正如您所料,这会大大增加评估SQL表达式所需的时间(在我们的测试数据库上从大约100ms到7秒),特别是如果数据树很复杂(因为单独的SQL调用来填充每个节点/树的子节点。)

如果我创建数据模型'inline ...

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)如果是这种情况,并且为此目的创建了特定的视图模型,那么在不必使用内联赋值块来构建表达式树的情况下,使用填充模型的正确方法是什么?

非常感谢任何帮助/建议。

2 个答案:

答案 0 :(得分:1)

问题是由于同时拥有数据层实体和域层实体并且需要在两者之间进行映射。虽然你可以让它工作,但这使得一切都非常复杂,正如你已经体验过的那样。您正在数据和域之间进行映射,并且很快会为这些相同的实体添加更多映射,因为性能原因以及其他业务逻辑和表示逻辑将需要不同的数据。

唯一真正的解决方案是抛弃数据实体并创建可直接序列化到后端存储(SQL服务器)的POCO模型对象。

从第一天起,LINQ to SQL就支持POCO实体,但我认为迁移到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和Entity Framework会将此转换为非常有效的SQL查询,该查询仅从数据库中返回人员姓名和公司位置,并在数据库中进行过滤(与GetEmployees()返回IEnumerable<T>时的.NET应用程序。

答案 1 :(得分:0)

您可以使用DataLoadOptions.LoadWith方法请求Linq2Sql预加载某些实体(而不是延迟加载它们),请参阅:http://msdn.microsoft.com/en-us/library/bb534268.aspx。 如果你使用Company实体执行此操作,那么我认为Linq2Sql将不必访问数据库以再次获取它。