代码之家  ›  专栏  ›  技术社区  ›  Vinko Vrsalovic

向实体模型中的LINQ添加行为

  •  1
  • Vinko Vrsalovic  · 技术社区  · 15 年前

    当使用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(); }
      
    •     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(); }
      

    有第四种方法吗?我不太了解LINQ的哲学,特别是关于实体的LINQ。

    2 回复  |  直到 15 年前
        1
  •  1
  •   Botz3000 Amir Sheng    15 年前

    据我所知,实体类是部分类,因此可以使用 partial 关键字。

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

        2
  •  1
  •   Craig Stuntz    15 年前

    实体框架是以实体数据模型为基础的,它的一位架构师将其描述为“非常接近.NET的对象数据模型,将行为模块化”。换句话说,实体模型的设计目的是将关系数据映射到对象空间,但不应使用方法对其进行扩展。保存业务类型的方法。

    与其他一些ORMs不同,您不会被黑盒中的任何对象类型所束缚。使用LINQ几乎可以投影到任何类型,即使它的形状与实体类型不同。因此,只将实体类型用于映射,而不用于业务代码、数据传输或表示模型。

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

    请考虑以下业务方法的示例:

    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); 
    }
    

    单一责任原则规定,一个班级只有一个改变的理由。在屏幕上显示的示例中,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作为单独的参数传递。但不管怎样,我们已经修复了挣到的helper对实体模型的依赖性,作为免费的奖励,我们还获得了更高效的SQL。

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

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

    这种架构的另一个好处是,它使Web程序集的单元测试变得非常容易。一般来说,单元测试不应该访问数据库(换句话说,访问数据库的测试是集成测试,而不是单元测试)。很容易编写一个模拟存储库,它将对象数组作为可查询文件返回,而不是实际转到实体框架。