向LINQ to Entities模型添加行为

时间:2009-09-02 21:06:37

标签: c# linq linq-to-entities

使用L2E向数据模型中的对象添加行为时,首选方法是什么?

  • 拥有一个包装器类,只需要您需要的数据就可以实现所需的行为

        using (var dbh = new ffEntities())
        {
            var query = from feed in dbh.feeds select 
                        new FFFeed(feed.name, new Uri(feed.uri), feed.refresh);
            return query.ToList();
        }
        //Later in a separate place, not even in the same class
        foreach (FFeed feed in feedList) { feed.doX(); }
    
  • 直接使用数据模型实例,并拥有一个在这些实例的IEnumerable上运行的方法

        using (var dbh = new ffEntities())
        {
            var query = from feed in dbh.feeds select feed;
            return query.ToList();
        }
        //Later in a separate place, not even in the same class
        foreach (feeds feed in feedList) { doX(feed); }
    
  • 在数据模型类上使用扩展方法,因此它最终会拥有包装器所具有的额外方法。

        public static class dataModelExtensions {
            public static void doX(this feeds source) {
                //do X
            }
        }
        //Later in a separate place, not even in the same class
        foreach (feeds feed in feedList) { feed.doX(); }
    

哪一个最好?我倾向于支持最后一种方法,因为它很干净,不会干扰CRUD设施(我可以直接使用它来插入/更新/删除,不需要包装回来),但我想知道是否有一个缺点我没有没见过。

有第四种方法吗?我没有把握LINQ的哲学,特别是关于LINQ to Entities。

2 个答案:

答案 0 :(得分:1)

据我所知,Entity类是部分类,因此您可以使用partial关键字直接添加另一个扩展它们的文件。

否则,我通常有一个包装类,即我的ViewModel(我正在使用带MVVM的WPF)。我还有一些带有流畅接口的通用Helper类,用于向ViewModel添加特定的查询过滤器。

答案 1 :(得分:1)

我认为将行为放在实体类型上是错误的。

实体框架基于实体数据模型,由其架构师之一描述为“非常接近.NET的对象数据模型,模拟行为”。换句话说,您的实体模型旨在将关系数据映射到对象空间,但不应使用方法进行扩展。保存业务类型的方法。

与其他一些ORM不同,您不会遇到任何来自黑匣子的对象类型。您可以使用LINQ投影到几乎任何类型,即使它的形状与您的实体类型不同。因此,仅使用实体类型进行映射,而不是使用业务代码,数据传输或表示模型。

生成代码时,实体类型被声明为部分。这导致一些开发人员尝试将它们扩展到业务类型。这是个错误。实际上,扩展实体类型并不是一个好主意。可以在LINQ to Entities中查询在实体模型中创建的属性;您添加到分部类的属性或方法不能包含在查询中。

考虑这些商业方法的例子:

public Decimal CalculateEarnings(Guid id)
{
    var timeRecord = (from tr in Context.TimeRecords
                      .Include(“Employee.Person”)
                      .Include(“Job.Steps”)
                      .Include(“TheWorld.And.ItsDog”)
                      where tr.Id = id
                      select tr).First();
    // Calculate has deep knowledge of entity model
    return EarningsHelpers.Calculate(timeRecord); 
}

这种方法有什么问题?生成的SQL将变得非常复杂,因为我们已经要求实体框架实现整个对象的实例,仅仅是为了获得Calculate方法所需的少数属性。代码也很脆弱。更改模型不仅会破坏急切加载(通过Include调用),还会破坏Calculate方法。

单一责任原则规定一个班级应该只有一个改变的理由。在屏幕上显示的示例中,EarningsHelpers类型负责实际计算收益和保持与实体模型的更新保持同步。第一个责任似乎是正确的,第二个责任听起来不对。让我们看看我们是否可以解决这个问题。

public Decimal CalculateEarnings(Guid id)
{
    var timeData = from tr in Context.TimeRecords
                   where tr.Id = id
                   select new EarningsCalculationContext
                   {
                       Salary = tr.Employee.Salary,
                       StepRates = from s in tr.Job.Steps
                                   select s.Rate,
                       TotalHours = tr.Stop – tr.Start
                   }.First();
    // Calculate has no knowledge of entity model
    return EarningsHelpers.Calculate(timeData); 
}

在下一个示例中,我重写了LINQ查询,仅选取Calculate方法所需的信息位,并将该信息投影到汇总Calculate方法参数的类型上。如果只是为了向方法传递参数而编写一个新类型似乎太多了,我也可以投射到一个匿名类型,并将Salary,StepRates和TotalHours作为单独的参数传递。但不管怎样,我们已经修复了EarningsHelpers对实体模型的依赖性,并且作为免费奖励,我们也获得了更高效的SQL。

您可能会查看此代码,并想知道如果TimeRecord的Job属性可以为空,会发生什么。我不会得到一个空引用异常吗?

不,我不会。此代码不会作为IL编译和执行;它将被翻译为SQL。 LINQ to Entities合并空引用。在屏幕上显示的示例查询中,如果Job为null,则StepRates将返回null。您可以将此视为与延迟加载相同,除非没有额外的数据库查询。代码说,“如果有工作,那么从其步骤加载费率。”

这种架构的另一个好处是它可以非常容易地对Web组件进行单元测试。一般来说,单元测试不应该访问数据库(换句话说,访问数据库的测试是集成测试而不是单元测试)。编写一个模拟存储库非常容易,它将对象数组作为Queryables返回,而不是实际进入实体框架。