2013-07-24 9 views
7

私はEntity Framework(EF)をORMとしてSQL Serverにリンクする新しいプロジェクトを開始しようとしています。私は、抽象概念に関してEFを扱うための良いアプローチが何であり、最終的に私のコードをテスト可能にするかを理解するために、大量の読書をしてきました。エンティティフレームワークよりも優れたリポジトリ抽象化を使用していますか?

問題は読むほどですが、私はもっと混乱しています。私はすべてそれになります。

私はロットから、彼らはインターフェースやテスト目的でモックやすい経由して簡単に、注射されているので、それらを作る、リポジトリのルートを下るつもりだった:私は行くには良い方法だと思った何

私が見つけたと思われる例のうち、このアプローチで誓いを立てる人がたくさんいます。 EFと対話するリポジトリパーツについては、Reposにビジネスロジックがないため、統合テストを行うつもりだったので、それを処理するService/Controllerクラスに移行しました。

一部は、リポジトリパターンは、他のリンクがあった不当な抽象

http://ayende.com/blog/3955/repository-is-the-new-singleton

ですが、私は、私は一種の取得あまりにも多くの

でこの質問を氾濫したくなかったと言いますこれは、私はEFとそれの実装は本質的に抽象ですが、私には具体的なもの(それはアプリケーションの範囲外に何かを打つ... DB)私の問題はpです私はおそらく私のサービス内からデータコンテキストと作業単位のデータとやりとりすることになるでしょうが、データアクセスログインとそのレベルのビジネスロジックを混在させているような感じです。それから私はユニットテストをどうやって行うのですか?

私は今、明確な方向性がなく、避けられないコーダーブロックが設定されているようです。私はきれいで、私はテストすることができるアプローチを探しています。

UPDATE - 私は

を始めるつもりですソリューション私はサイモンによって与えられた答えの1、その後わずかに異なるパスを行ってきましたが、私は読んでいたが、彼は私ので、再投稿記事のおかげで再びそれを乗り越えた。まず、私は既存のデータベースを持っていて、何らかの理由でデザイナーツールが気に入らないので、Code Firstを使用しています。私はいくつかのシンプルなPOCOオブジェクトといくつかのマッピングを作成しました。ここでは単純にしていますが、私はちょうど1つのPOCO/Mappingを表示します。

POCO

public abstract class BaseEntity<T> 
{ 
    [Key] 
    public T Id { get; set; } 
    public string Name { get; set; } 
    public DateTime CreatedOn { get; set; } 
}  

public class Project : BaseEntity<int> 
{ 
    public virtual ICollection<Site> Sites { get; set; } 
    public bool Active { get; set; } 
}  

作業インターフェースの単位

public interface IUnitOfWork 
{ 
    IDbSet<Project> Projects { get; } 

    void Commit(); 
} 

コンテキスト

internal class MyContext : DbContext 
{ 
    public MyContext(string connectionString) 
     : base(connectionString) 
    { 

    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Configurations.Add(new ProjectMap()); 
    } 
} 

コンクリートのUnitOfWork

public class UnitOfWork : IUnitOfWork 
{ 
    readonly MyContext _context; 
    const string ConnectionStringName = "DBConn"; 

    public UnitOfWork() 
    { 
     var connectionString = ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString; 
     _context = new FosilContext(connectionString); 
    } 

    public IDbSet<Project> Projects 
    { 
     get { return _context.Set<Project>(); } 
    } 

    public void Commit() 
    { 
     _context.SaveChanges(); 
    } 
} 

基本的なテストコード

 var _repo = new UnitOfWork().Projects; 
     var projects = from p in _repo 
         select p; 

     foreach (var project in projects) 
     { 
      Console.WriteLine(string.Format("{0} - {1} - {2} - {3})", project.Id, project.Name, project.Active.ToString(), project.CreatedOn.ToShortDateString())); 
     } 

     Console.Read(); 

たUnitOfWorkは自明であるが、具体的な状況に基づいて、私は偽IUnitOfWorkを作成し、テストのために偽IDBSetに渡し。私にとっては、これは構造的に私を噛ませるために戻ってくるかもしれませんが、私はこれに入り込みますが、この抽象的なEFの上にRepoはありません。記事ではIDbSetはRepoと同等なので、私が使っていることを説明しています。私はUoWの背後に私のカスタム・コンテキストを隠しているんだけど、どうやってそれがどうやって見えるのか見ていきます。

私は今、データやビジネスルールの検索をカプセル化するサービスレイヤーでこれを使用できますが、EF固有のアイテムを偽装することができるようにユニットテスト可能でなければならないと考えています。うまくいけば、それは私のコントローラがかなりリーンになりますが、私たちは:)

+0

Entity Frameworkを使用している場合は、UnitOfWorkに似た 'DbContext'とリポジトリに似た' DbSet'があることを忘れないでください。この質問は個人的な意見を招き、Stack Overflowには適していないかもしれませんが、Entity Frameworkを使用する場合は、パターンを受け入れる可能性があります。 'DbContext'と' DbSet'sをさらにラッピングすることは、実際には、単体テストの容易さとある程度の依存性注入のために、コンクリートクラスに対して非常に薄い抽象化を与えることです。 –

+0

コメントありがとうございます。私が得意でないもの(私はEFにとって非常に新しい)は、この薄い層がどのように見えるかです。私は具体的にEFについてのビデオを見ていて、作者はIDetetを使ってIContextインターフェースを導入しましたが、別の作業単位(これらの例はリポジトリを使用しました)が渡されました。薄い層はIDBSetとIContextでした。作者は、リポジトリパターンの問題とまったく同じように見える抽象概念を使ってEFを隠していると指摘しましたか? – Modika

+0

私はテキストの壁を以前の答えに戻しています。文字の制限により、書式設定が少し難しくなります。 –

答えて

7

あなたはEntity Frameworkのを使用している場合は、DbContextはUnitOfWorkのに似DbSetであることを忘れてはいけないでしょうリポジトリに似です。この質問は個人的な意見を招き、Stack Overflowには適していないかもしれませんが、Entity Frameworkを使用する場合は、パターンを受け入れる可能性があります。さらにDbContextDbSetをラップすることは、実際には、単体テストの容易さとある程度の依存性注入のために、コンクリートクラスに対して非常に薄い抽象化を与えることです。

MSDN(Testability and Entity Framework 4.0)には、良い出発点を与えるトピックがあります。ここには、Entity Frameworkのコンテキストでこのパターンを実装する方法に関する提案がいくつかあります。ほとんどの場合、SaveChanges()を実行するにはDbContextを使用し、DbSet<T>に実装するIQueryable<T>の助けを借りてCRUD操作を行うにはDbSet<T>を使用します。 DbContextで必要なものを模倣するようにIUnitOfWorkを実装し、DbSetでも同じことができます。

という点で実装がほとんどDbContextDbSet<T>のメソッドの1対1のマッピングになりますが、あなたはまた、インメモリIRepository<T>(例えばHashSet Sを使用して)に基づく実装するカスタムIUnitOfWorkを実装することが可能Queryableコンポーネントに対するクエリロジックの単体テストを容易にします。リポジトリパターンは良い考えですか?これは議論の余地があります。それは本当にあなたが必要とするプロジェクトと柔軟性と、あなたの異なるレイヤーが望むもの(そしてあなたが望んでいないもの)に依存します。

編集コメントに答えるために: あなたUnitOfWork実装がDbContextを継承しますが、インスタンスを作成し、民間のそれを維持し、コンテキストに独自のメソッド呼び出しを委任するべきではありません。 Repository<T>の実装では、おそらく内部コンストラクタでDbSet<T>が使用されます。モデル・ファーストのアプローチの大きな利点の1つは、組立組織に与えられた自由です。私が通常やっていることは、モデルとDbContextを2つの別々のアセンブリで分離することです。後者は前者を参照しています。このシナリオでは、次のようになります。

1 - あなたのData.dllアセンブリには、インターフェイスと実装が含まれています - あなたのModels.dllアセンブリは、あなたのPOCO

2が含まれています。また、データアクセスレイヤーに関するインターフェイスと一般的なユーティリティを含むData.Core.dll(たとえば)があり、実際の実装には互換性のあるData.Entity.dll(またはData.List.dll)があります。 (あなたがたとえば、依存性注入を使用する場合)

このシナリオで
public interface IUnitOfWork { /* Your methods */ } 

public interface IRepository<T> where T : class { /* Your methods */ } 

internal class YourDbContext : DbContext { /* Your implementation */ } 

public class YourDatabaseContext : IUnitOfWork 
{ 
    private readonly YourDbContext dbContext; 

    public YourDatabaseContext() 
    { 
     // You could also go with the Lazy pattern here to defer creation 
     dbContext = new YourDbContext(); 
    } 
} 

internal class DbSetRepository<T> : IRepository<T> where T : class 
{ 
    private readonly DbSet<T> dbSet; 

    public DbSetRepository(DbSet<T> dbSet) 
    { 
     // You could also use IDbSet<T> for a toned down version 
     this.dbSet = dbSet; 
    } 
} 

、あなただけのインターフェイスとあなたのIUnitOfWork実装は、アセンブリの外に表示されている:我々は最初のオプションで行くならば、それは次のようになります。これは重要ではなく、実際にはデザインの選択肢の問題です。あなたのDbContextはあなたの内部実装の定義によってあなたのPOCOに「リンク」され、あなたの設定ですべてが配線されます。

+0

ありがとうございました。昨日読んだことがありますが、コンテキストの代わりにUoWを使用しているので、名前が混乱していたようですが、ここでは互換性があります。一つのこと(しかしこれは本当にこれに関連していません)私はデータベースに私のモデルをリンクするマッピングを追加する場所を見つけることができなかったので、私は他の場所を見ようとしました。 – Modika

+0

@Modikaマッピングとはどういう意味ですか?あなたのEDMX? –

+0

私はEDMXを使用していません。私はコードの最初のアプローチを行っていましたが、POCOをデータベース表現に接続するマッピングです。私は、EntityTypeConfigurationマッピングの束を持っていますが、私はその記事を見て、具体的なコンテキスト(UoF)の実装はDbContextから継承することができ、次にOnModelCreatingメソッドにアクセスできます。 ... – Modika

0

私たちは、独自のORM抽象化フレームワークを使用して、リポジトリ、作業単位および仕様パターンを数年間サポートしてきました。私たちの経験に基づいて、私は間違いなく「はい!

このような抽象化は、必ずしも互換性を保証するものではありません。もう一つの非常に重要な利点は、拡張性です。

たとえば、あるプロジェクトでは、リリース後、新しい要件は一部のエンティティの「公開済み」プロパティであり、UIは未公開のエンティティを非表示にする必要がありました。このシナリオでは、単一のDbContextに対して行われたすべてのクエリに適用されるHibernateの「セッションフィルタ」と非常に似ている「デフォルト仕様」をサポートするように、フレームワークを拡張しました。我々はすでに(owinおよび.NETコアベースのアプリケーションで、今、実際に好ましい方法である)DbContextライフサイクルシステムを「要求がスコープ」しているので、私たちは、このようなAPIを追加するために、それは非常に簡単でした

CurrentContext.AddDefaultSpec(new Spec<Product>(t => t.Published)) 

この呼び出しは、現在のコンテキストで製品の仕様のリストに与えられた仕様を追加し、当社の仕様のシステムは、すでに複数のラムダから表現をマージすることができた。もちろん、

Repository<Product>.Query().Where(....) 

上で実行式を照会するすべての製品の仕様を適用し、仕様ですがここでは範囲外です。

この機能を実行するために、単純なASP .NET MVCフィルタ属性を開発しました。この属性を置くほとんどのコントローラアクション(管理部分を除く)では、

注:syncメソッドとasyncメソッドの両方を抽象化することは非常に難しく、努力する価値がありませんでした。また、.Include()などのEF固有の呼び出しが必要でした。クライアントコードには、 EFを別のORMに置き換える必要がある場合は役に立ちません。しかし、これは必須条件ではなく、実際にはほとんどのプロジェクトでこのような要件を満たすことはできません。

リポジトリで抽象化する理由は、「理想的な」プラットフォームに依存しないAPIを作成するだけで、異なるORMやデータアクセスインターフェイスをターゲットにする汎用フレームワークを構築していない場合は、正しい方法です。

私たちの主な目的は、追加のユーティリティ、共通のデザインパターン、イベント駆動型のカスタマイズ可能な作業単位(つまり、何らかのタイプのオブジェクトが追加されたときのイベントの観察)をサポートするEFに基づいた社内フレームワークを作成することでした。それは問題の適切な解決策でした。実装するのはそれほど簡単ではありませんでしたが。

関連する問題