2

データ アクセスに C# / MVC 4 と Entity Framework 5 を使用して、新しい Web プロジェクトを開始しています。プロジェクトの構造に n 層のアプローチを採用することにしました。設計に関する決定についてフィードバックをお願いします。

ソリューションの構造は次のとおりです。

Project.Model (クラス ライブラリ): EF .edmx、エンティティ モデル、およびビュー モデルが含まれています

Project.DAL (クラス ライブラリ): EF DbContext およびリポジトリ クラスが含まれています

Project.BLL (クラス ライブラリ): ビジネス ロジック クラスが含まれています。

プロジェクト (MVC プロジェクト)

ダル

データ アクセス レイヤーは、単純な CRUD のような操作にのみ関係します。私はリポジトリアプローチを採用することにしました。リポジトリ インターフェイスは次のとおりです。

public interface IRepository
{
}

public interface IRepository<T> : IRepository, IDisposable 
    where T : class, new()
{
    T Add(T item);

    T Get(object id);

    T Get(Expression<Func<T, bool>> predicate);

    IQueryable<T> GetAll();

    IQueryable<T> GetAll(Expression<Func<T, bool>> predicate);

    void Update(T item);

    void Delete(T item);
}

Web プロジェクトでの Entity Framework の使用について調査した結果、一般的なコンセンサスは、要求ごとに DbContext/ObjectContext を 1 つだけにする必要があるということです。そこで、リクエストごとに 1 つのコンテキストを作成して破棄するために、DbContext を HttpContext に挿入する HttpModule を作成しました。

public class DbContextModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += context_BeginRequest;
        context.EndRequest += context_EndRequest; 
    }

    public void Dispose()
    {
    }

    private void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpContext httpContext = application.Context;

        httpContext.Items.Add(Repository.ContextKey, new ProjectEntities());
    }

    private void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpContext httpContext = application.Context;

        var entities = (ProjectEntities)httpContext.Items[Repository.ContextKey];

        entities.Dispose();
        entities = null;

        application.Context.Items.Remove(Repository.ContextKey);
    }
}

次はリポジトリ基本クラスです。コンストラクターは、上記の HttpModule から挿入された DbContext を利用することに注意してください。

public abstract class Repository<T> : IRepository<T> where T : class, new()
{
    protected Repository()
    {
        if (HttpContext.Current == null)
        {
            throw new Exception("Cannot create repository - current HttpContext is null.");
        }

        _entities = (ProjectEntities)HttpContext.Current.Items[Repository.ContextKey];

        if (_entities == null)
        {
            throw new Exception("Cannot create repository - no DBContext in the current HttpContext.");
        }
    }

    private ProjectEntities _entities;

    public T Add(T item)
    {
        _entities.Set<T>().Add(item);
        _entities.SaveChanges();

        return item;
    }

    public T Get(object id)
    {
        return _entities.Set<T>().Find(id);
    }

    public T Get(Expression<Func<T, bool>> predicate)
    {
        return _entities.Set<T>().AsQueryable().FirstOrDefault(predicate);
    }

    public IQueryable<T> GetAll()
    {
        return _entities.Set<T>().AsQueryable();
    }

    public IQueryable<T> GetAll(Expression<Func<T, bool>> predicate)
    {
        return _entities.Set<T>().AsQueryable().Where(predicate);
    }

    public void Update(T item)
    {
        _entities.Entry(item).State = EntityState.Modified;
        _entities.SaveChanges();
    }

    public void Delete(T item)
    {
        _entities.Set<T>().Remove(item);
        _entities.SaveChanges();
    }
}

そして、実装の簡単な例...

public class AdminRepository : Repository<Admin>
{
    public Admin GetByEmail(string email)
    {
        return Get(x => x.Email == email);
    }
}

BLL

ビジネス ロジック層は、すべてのビジネス ロジックをカプセル化します。制約を維持するために、次のような基本「ロジック」クラスを作成しました。

public abstract class Logic<TRepository> where TRepository : class, IRepository, new()
{
    private static Expression<Func<TRepository>> _x = () => new TRepository();
    private static Func<TRepository> _compiled = _x.Compile(); 

    protected Logic()
    {
        Repository = _compiled();
    }

    protected internal TRepository Repository { get; private set; }
}

コンストラクターは必要な Repository クラスを自動的に作成するため、リポジトリをインスタンス化するために子クラスに追加のコードは必要ありません。これは実装の簡単な例です

public class AdminLogic : Logic<AdminRepository>
{
    public Admin Add(Admin admin)
    {
        return Repository.Add(admin);
    }

    public Admin Get(object id)
    {
        return Repository.Get(id);
    }

    public Admin GetByEmail(string email)
    {
        return Repository.GetByEmail(email);
    }

    public IQueryable<Admin> GetAll()
    {
        return Repository.GetAll();
    }

    public void Update(Admin admin)
    {
        Repository.Update(admin);
    }
}

この例は、DAL リポジトリのパススルーに近いものですが、後でビジネス ロジックを追加しても問題ありません。遅延実行のために IQueryable を必要とするいくつかのサード パーティ ツールを使用しているため、BLL から IQueryable を返すことを選択しています。

プロジェクト (MVC プロジェクト)

最後に、単純なコントローラー アクションは次のようになります。

public ActionResult Index(int? page)
{
    // Instantiate logic object
    AdminLogic logic = new AdminLogic();

    // Call GetAll() and use AutoMapper to project the results to the viewmodel
    IQueryable<AdminModel> admins = logic.GetAll().Project().To<AdminModel>();

    // Paging (using PagedList https://github.com/TroyGoode/PagedList)
    IPagedList<AdminModel> paged = admins.ToPagedList(page ?? 1, 25);

    return View(paged);
}

すべてが期待どおりに機能し、テストでは EF コンテキストが適切に破棄され、全体的な速度が良好であることが示されています。

これはこれを行うためのかなり良い方法ですか?

お時間をいただきありがとうございます。

4

1 に答える 1

2

リポジトリパターンを使用する場合は、リポジトリを集約と見なす必要があると思います。リポジトリを実行すると、ある程度の抽象化が可能になりますが、そのようにすると、リポジトリがエンティティ中心になり、時にはそれが困難になる可能性があります同じ集約ルートに属する他のオブジェクト通信するため。

私はしばらく前にそのジレンマを抱えていました.repository(T)を使用する代わりに、「汎用リポジトリ」内でmethods(T)を使用することになりました。これにより、集約ルートに属する他のオブジェクトの操作が少し簡単になりました(私それは私には合っていたのでこれを行いましたが、それがあなたに適しているという意味ではありません.あなたがそれを考慮に入れるためだけにこの例を表に載せています)、あなたはこの質問これを見てください.私が非常に興味深いと思った記事。

Jeremy miller がIRepositoryを実装する方法について語っています。

public interface IRepository { // Find an entity by its primary key // We assume and enforce that every Entity // is identified by an "Id" property of // type long T Find<T>(long id) where T : Entity; // Query for a specific type of Entity // with Linq expressions. More on this later 
IQueryable<T> Query<T>(); 
IQueryable<T> Query<T>(Expression<Func<T, bool>> where); // Basic operations on an Entity 
void Delete(object target); 
void Save(object target); 
void Insert(object target); 
T[] GetAll<T>(); }

これは、レポで行ったこととほとんど同じです。

潜在的に、(要件に応じて) さらに 2 つのレイヤーが必要になると思います。1 つは、アプリケーション全体で一般的な操作またはアクションを処理するサービス用であり、コンポーネント (電子メール、ログ、キャッシュ マネージャー、暗号化、ヘルパーなど) 用の別のレイヤーです。

BLのロジックをどのように処理しているかについては、私にはやり過ぎですが、実際にはリポジトリをロジックに結合していますが、個人的にはそれは問題ないと思います。

アプリケーションに依存性注入を実装してみてください。多くの利点が得られるからです。

BL のログイン方法を持つ UserService を作成するとします。

public class UserService:IService {
    public UserService(IUserRepository, IMailer, ILogger){
      // for example you can follow the next use case in your BL
      // try to login, if failed reteat after 3 time you block the accunt and send a mail

    }

   public bool login(string username, string password){
   }

}

次に、そのサービスをコントローラーに実装できます(コンテナーを使用している場合はコンストラクターに挿入し、ログインサービスを呼び出すだけです)。これにより、実装がよりクリーンになります。

public ActionResult Index(){
  //then you're going to be able to use  _userService.login()
}

そうすれば、疎結合のアプリケーションが作成され、理論的には保守が容易になります。

ちょうど私の2セント

于 2013-09-25T20:21:56.263 に答える