【问题标题】:Problem using LINQ to SQL with one DataContext per atomic action使用 LINQ to SQL 时出现问题,每个原子操作都有一个 DataContext
【发布时间】:2008-11-03 18:32:18
【问题描述】:

我已经开始在一个(有点像 DDD 的)系统中使用 Linq to SQL,它看起来(过于简化)如下:

public class SomeEntity // Imagine this is a fully mapped linq2sql class.
{
    public Guid SomeEntityId { get; set; }
    public AnotherEntity Relation { get; set; }
}

public class AnotherEntity // Imagine this is a fully mapped linq2sql class.
{
    public Guid AnotherEntityId { get; set; }
}

public interface IRepository<TId, TEntity>
{
    Entity Get(TId id);
}

public class SomeEntityRepository : IRepository<Guid, SomeEntity>
{
    public SomeEntity Get(Guid id)
    {
        SomeEntity someEntity = null;
        using (DataContext context = new DataContext())
        {
            someEntity = (
                from e in context.SomeEntity
                where e.SomeEntityId == id
                select e).SingleOrDefault<SomeEntity>();
        }

        return someEntity;
    }
}

现在,我遇到了一个问题。当我尝试像这样使用 SomeEntityRepository 时

public static class Program
{
    public static void Main(string[] args)
    {
        IRepository<Guid, SomeEntity> someEntityRepository = new SomeEntityRepository();
        SomeEntity someEntity = someEntityRepository.Get(new Guid("98011F24-6A3D-4f42-8567-4BEF07117F59"));
        Console.WriteLine(someEntity.SomeEntityId);
        Console.WriteLine(someEntity.Relation.AnotherEntityId);
    }
 }

在程序到达最后一个 WriteLine 之前一切正常,因为它会抛出一个 ObjectDisposedException,因为 DataContext 不再存在。

我确实看到了实际问题,但我该如何解决呢?我想有几种解决方案,但迄今为止我所想到的没有一个适合我的情况。

  • 摆脱存储库模式,并为每个基本工作部分使用新的 DataContext。
    • 我真的不想这样做。一个原因是我不想让应用程序知道存储库。另一个是我不认为让 linq2sql 的东西 COM 可见会很好。
    • 另外,我认为context.SubmitChanges() 的承诺可能会比我预期的要多。
  • 指定 DataLoadOptions 以获取相关元素。
    • 由于我希望我的业务逻辑层在某些情况下只回复某些实体,我不知道他们需要使用哪些子属性。
  • 禁用所有属性的延迟加载/延迟加载。
    • 不是一个选项,因为有很多表,而且它们的链接很紧密。这可能会导致大量不必要的流量和数据库负载。
  • 互联网上的一些帖子说使用 .Single() 应该会有所帮助。
    • 显然不是...

有什么办法可以解决这个问题吗?

顺便说一句:我们决定使用 Linq t0 SQL,因为它是一个相对轻量级的 ORM 解决方案,并且包含在 .NET 框架和 Visual Studio 中。如果 .NET 实体框架更适合这种模式,则可以选择切换到它。 (我们在实现方面还没有那么远。)

【问题讨论】:

    标签: c# linq-to-sql persistence ddd-repositories


    【解决方案1】:

    Rick Strahl 在这里有一篇关于 DataContext 生命周期管理的精彩文章:http://www.west-wind.com/weblog/posts/246222.aspx

    基本上,原子操作方法在理论上很好,但您需要保留 DataContext 以便能够跟踪数据对象中的更改(并获取子对象)。

    另请参阅:Multiple/single instance of Linq to SQL DataContextLINQ to SQL - where does your DataContext live?

    【讨论】:

    • 谢谢,您是否有任何代码示例可以将数据上下文存储在加载的实体中而不会带来太多麻烦和开销?另外,这是否意味着任何实体都必须实现 IDisposable 以便我可以正确处理 DataContext?
    • 事实是,有一些麻烦和开销。我评估了文章中的每个方法,并确定 DataContext/obj 是最不邪恶的。 IDisposable 是一个好主意,尽管您的连接会在所有记录都用完后关闭:msdn.microsoft.com/en-us/library/bb386929.aspx - FAQ 3
    【解决方案2】:

    您必须:

    1) 让上下文保持打开状态,因为您尚未完全决定将使用哪些数据(也称为延迟加载)。

    或 2) 如果您知道需要其他属性,请在初始加载时提取更多数据。

    对后者的解释:here

    【讨论】:

      【解决方案3】:

      如果您使用原子工作单元,我不确定您是否必须放弃存储库。我同时使用这两种方法,尽管我承认放弃了乐观并发检查,因为它们无论如何都不能分层工作(不使用时间戳或其他一些必需的约定)。我最终得到的是一个使用 DataContext 并在完成后将其丢弃的存储库。

      这是一个不相关的 Silverlight 示例的一部分,但前三个部分展示了我如何使用存储库模式和一次性 LINQ to SQL 上下文,FWIW:http://www.dimebrain.com/2008/09/linq-wcf-silver.html

      【讨论】:

        【解决方案4】:

        指定 DataLoadOptions 以获取相关元素。因为我希望我的业务逻辑层在某些情况下只回复一些实体,所以我不知道他们需要使用哪些子属性。

        如果调用者被授予使用 .Relation 属性所需的耦合,那么调用者也可以指定 DataLoadOptions。

        DataLoadOptions loadOptions = new DataLoadOptions();
        loadOptions.LoadWith<Entity>(e => e.Relation);
        SomeEntity someEntity = someEntityRepository
          .Get(new Guid("98011F24-6A3D-4f42-8567-4BEF07117F59"),
          loadOptions);
        

        //

        using (DataContext context = new DataContext())
        {
          context.LoadOptions = loadOptions;
        

        【讨论】:

        • 调用者可以肯定地指定加载选项,但我希望系统不要将此泄漏到业务逻辑层中。另外,为什么访问 someEntity.Relation 属性与指定 DataLoadOptions 相关?
        【解决方案5】:

        这就是我所做的,到目前为止效果非常好。

        1) 使 DataContext 成为存储库中的成员变量。是的,这意味着您的存储库现在应该实现 IDisposable 并且不要保持打开状态......也许您想避免必须做的事情,但我没有发现它不方便。

        2) 像这样向您的存储库添加一些方法:

        public SomeEntityRepository WithSomethingElseTheCallerMightNeed()
        {
         dlo.LoadWith<SomeEntity>(se => se.RelatedEntities);
         return this; //so you can do method chaining
        }
        

        然后,你的调用者看起来像这样:

        SomeEntity someEntity = someEntityRepository.WithSomethingElseTheCallerMightNeed().Get(new Guid("98011F24-6A3D-4f42-8567-4BEF07117F59"));
        

        您只需要确保当您的存储库访问 db 时,它会使用这些辅助方法中指定的数据加载选项...在我的情况下,“dlo”被保留为成员变量,然后在点击之前设置数据库。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-02-09
          • 2010-09-19
          • 2011-01-13
          • 1970-01-01
          • 2012-02-21
          • 1970-01-01
          • 2020-11-24
          相关资源
          最近更新 更多