将 LINQ to SQL 与每个原子操作一个 DataContext 结合使用时出现问题
我已经开始在一个(有点类似 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,因为它会抛出 ObjectDisposeException
,因为 DataContext 不再存在。
我确实看到了实际问题,但我该如何解决这个问题呢? 我想有几种解决方案,但迄今为止我想到的解决方案都不适合我的情况。
- 摆脱存储库模式并为工作的每个原子部分使用新的 DataContext。
- 我真的不想这样做。 一个原因是我不想让应用程序知道存储库。 另一个问题是我不认为让 linq2sql 的东西 COM 可见会很好。
- 此外,我认为执行
context.SubmitChanges()
可能会比我预期的要多得多。
- 指定 DataLoadOptions 以获取相关元素。
- 由于我希望业务逻辑层在某些情况下仅回复某些实体,因此我不知道它们需要使用哪些子属性。
- 禁用所有属性的延迟加载/延迟加载。
- 这不是一个选择,因为有相当多的表,而且它们之间的链接很紧密。 这可能会导致大量不必要的流量和数据库负载。
- 互联网上的一些帖子说使用 .Single() 应该有所帮助。
- 显然它不...
有什么办法可以解决这个痛苦吗?
顺便说一句:我们决定使用 Linq t0 SQL,因为它是一个相对轻量级的 ORM 解决方案,并且包含在 .NET 框架和 Visual Studio 中。 如果 .NET 实体框架更适合此模式,则可能可以选择切换到它。 (我们的实施还没有那么远。)
I have started using Linq to SQL in a (bit DDD like) system which looks (overly simplified) like this:
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;
}
}
Now, I got a problem. When I try to use SomeEntityRepository like this
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);
}
}
everything works nicely until the program gets to the last WriteLine, because it throws an ObjectDisposedException
, because the DataContext does not exist any more.
I do see the actual problem, but how do I solve this? I guess there are several solutions, but none of those I have thought of to date would be good in my situation.
- Get away from the repository pattern and using a new DataContext for each atomic part of work.
- I really would not want to do this. A reason is that I do not want to be the applications to be aware of the repository. Another one is that I do not think making linq2sql stuff COM visible would be good.
- Also, I think that doing
context.SubmitChanges()
would probably commit much more than I intended to.
- Specifying DataLoadOptions to fetch related elements.
- As I want my Business Logic Layer to just reply with some entities in some cases, I do not know which sub-properties they need to use.
- Disabling lazy loading/delayed loading for all properties.
- Not an option, because there are quite a few tables and they are heavily linked. This could cause a lot of unnecessary traffic and database load.
- Some post on the internet said that using .Single() should help.
- Apparently it does not ...
Is there any way to solve this misery?
BTW: We decided to use Linq t0 SQL because it is a relatively lightweight ORM solution and included with the .NET framework and Visual Studio. If the .NET Entity Framework would fit better in this pattern, it may be an option to switch to it. (We are not that far in the implementation, yet.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
Rick Strahl 有一篇关于 DataContext 生命周期管理的好文章:http://www. west-wind.com/weblog/posts/246222.aspx。
基本上,原子操作方法在理论上很好,但您需要保留 DataContext 以便能够跟踪数据对象中的更改(并获取子对象)。
另请参阅:Linq to SQL DataContext 的多个/单个实例 和 < a href="https://stackoverflow.com/questions/196253/linq-to-sql-where-does-your-datacontext-live">LINQ to SQL - 您的 DataContext 位于何处?。
Rick Strahl has a nice article about DataContext lifecycle management here: http://www.west-wind.com/weblog/posts/246222.aspx.
Basically, the atomic action approach is nice in theory but you're going to need to keep your DataContext around to be able to track changes (and fetch children) in your data objects.
See also: Multiple/single instance of Linq to SQL DataContext and LINQ to SQL - where does your DataContext live?.
您必须:
1)保持上下文打开,因为您还没有完全决定将使用哪些数据(又名延迟加载)。
或 2) 如果您知道需要其他属性,则在初始加载时提取更多数据。
后者的解释:此处
You have to either:
1) Leave the context open because you haven't fully decided what data will be used yet (aka, Lazy Loading).
or 2) Pull more data on the initial load if you know you will need that other property.
Explaination of the latter: here
我不确定如果您使用原子工作单元,您是否必须放弃存储库。 我使用两者,尽管我承认放弃了乐观并发检查,因为它们无论如何都不能分层工作(不使用时间戳或其他一些必需的约定)。 我最终得到的是一个使用 DataContext 的存储库,并在完成后将其丢弃。
这是一个不相关的 Silverlight 示例的一部分,但前三个部分展示了我如何使用具有一次性 LINQ to SQL 上下文的存储库模式,FWIW: http://www.dimebrain.com/2008/09/linq-wcf-silver.html
I'm not sure you have to abandon Repository if you go with atomic units of work. I use both, though I admit to throwing out the optimistic concurrency checks since they don't work out in layers anyway (without using a timestamp or some other required convention). What I end up with is a repository that uses a DataContext and throws it away when it's done.
This is part of an unrelated Silverlight example, but the first three parts show how I'm using a Repository pattern with a throwaway LINQ to SQL context, FWIW: http://www.dimebrain.com/2008/09/linq-wcf-silver.html
如果调用者被授予使用 .Relation 属性所需的耦合,那么调用者也可以指定 DataLoadOptions。
//
If the caller is granted the coupling necessary to use the .Relation property, then the caller might as well specify the DataLoadOptions.
//
这就是我所做的,到目前为止效果非常好。
1) 使 DataContext 成为存储库中的成员变量。 是的,这意味着您的存储库现在应该实现 IDisposable 而不是保持打开状态...也许您想避免这样做,但我没有发现它有什么不方便。
2)向您的存储库添加一些方法,如下所示:
然后,您的调用者如下所示:
您只需要确保当您的存储库访问数据库时,它使用这些辅助方法中指定的数据加载选项...在我的例子中“dlo”保留为成员变量,然后在访问数据库之前设置。
This is what I do, and so far it's worked really well.
1) Make the DataContext a member variable in your repository. Yes, this means you're repository should now implement IDisposable and not be left open... maybe something you want to avoid having to do, but I haven't found it to be inconvenient.
2) Add some methods to your repository like this:
Then, your caller looks like this:
You just need to make sure that when your repository hits the db, it uses the data load options specified in those helper methods... in my case "dlo" is kept as a member variable, and then set right before hitting the db.