アトミック アクションごとに 1 つの DataContext を使用した LINQ to SQL の使用に関する問題
-
06-07-2019 - |
質問
私は、次のような(過度に単純化された)(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 を使用します。
- 本当にこんなことはしたくないです。理由は、アプリケーションにリポジトリを意識させたくないからです。もう 1 つは、linq2sql の内容を COM で見えるようにするのが良いとは思えないということです。
- また、そうすることは、
context.SubmitChanges()
おそらく私が意図していたよりもはるかに多くのことをコミットするでしょう。
- DataLoadOptions を指定して関連要素をフェッチします。
- 場合によっては、ビジネス ロジック レイヤーがいくつかのエンティティで応答するようにしたいのですが、どのサブプロパティを使用する必要があるかわかりません。
- すべてのプロパティの遅延読み込み/遅延読み込みを無効にします。
- かなりの数のテーブルがあり、それらは高度にリンクされているため、オプションではありません。これにより、大量の不要なトラフィックとデータベースの負荷が発生する可能性があります。
- インターネット上のいくつかの投稿では、.Single() を使用すると役立つはずだと書かれていました。
- どうやらそうではないようです...
この悲惨さを解決する方法はあるのでしょうか?
ところで:Linq t0 SQL は比較的軽量な ORM ソリューションであり、.NET Framework と Visual Studio に含まれているため、Linq t0 SQL を使用することにしました。.NET Entity Framework がこのパターンにより適している場合は、それに切り替えることが選択肢になる可能性があります。(まだ実装はそこまで進んでいません。)
解決
Rick Strahlには、DataContextのライフサイクル管理に関する素晴らしい記事があります: http:/ /www.west-wind.com/weblog/posts/246222.aspx 。
基本的に、アトミックアクションアプローチは理論上は優れていますが、データオブジェクトの変更を追跡(および子を取得)できるように、DataContextを保持する必要があります。
参照: Linq to SQL DataContextの複数/単一インスタンスおよび LINQ to SQL-DataContextはどこにありますか? 。
他のヒント
次のいずれかを行う必要があります:
1)使用するデータをまだ完全に決定していないため、コンテキストを開いたままにします(別名、遅延読み込み)。
または2)他のプロパティが必要になることがわかっている場合は、初期ロードでさらにデータを取得します。
後者の説明:ここ
アトミックな作業単位を使用する場合、リポジトリを放棄する必要があるかどうかはわかりません。私は両方を使用しますが、楽観的な同時実行チェックは(タイムスタンプやその他の必要な規則を使用せずに)レイヤーでうまくいかないため、破棄することを認めています。最終的には、DataContextを使用し、処理が完了すると破棄するリポジトリです。
これは無関係なSilverlightの例の一部ですが、最初の3つの部分は、使い捨てのLINQ to SQLコンテキストFWIWでリポジトリパターンを使用する方法を示しています: http://www.dimebrain.com/2008/09/linq-wcf-silver.html
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;
これは私がしていることであり、今のところうまく機能しています。
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に達すると、それらのヘルパーメソッドで指定されたデータロードオプションを使用することを確認する必要があります...はメンバー変数として保持され、dbにアクセスする直前に設定されます。