2

フロントエンドにWebフォームを使用し、管理コンソールにMVCを使用するソリューションがあります。

どちらのUIもNinjectを介してサービスレイヤーを使用しますが、微妙ではありますがかなり重要な問題を解決するのに問題があります。

文字列検索用語に基づいてコースのリストを返すCourseServiceがあるとします。このサービスは検索結果を返しますが、管理情報の目的で、行われた検索とその用語に一致するコースの数も記録する必要があります。

私は、リクエストの最後に、ボタンクリックイベントなどのページメソッドで、作業単位がUIによってコミットされるという考えから始めました。同じことがコントローラーにも当てはまります。

ここでの問題は、検索をログに記録するために、UI開発者が作業ユニットでCommit()を呼び出すことに依存していることです。UI開発者は、commitを呼び出さなくても問題なく続行でき、結果が返されますが、検索はログに記録されません。これは、サービス層に作業単位の範囲を制御させるという決定につながります。Ninjectは、作業単位をサービスレイヤーとリポジトリ実装の両方に自動的に渡します。これは、リクエストスコープごとに作成するようにninjectに指示したのと実質的に同じインスタンスになります。

これが私のレイヤーの書き方の例です...

public class CourseService
{
    private readonly ICourseRepository _repo;

    public CourseService(ICourseRepository repo)
    {
        _repo = repo;
    }

    public IEnumerable<Course> FindCoursesBy(string searchTerm)
    {
        var courses = _repo.FindBy(searchTerm);
        var log = string.format("search for '{1}' returned {0} courses",courses.Count(),searchTerm);
        _repo.LogCourseSearch(log);
        //IMO the service layer should be calling Commit() on IUnitOfWork here...
        return courses;
    }
}

public class EFCourseRepository : ICourseRepository
{
    private readonly ObjectContext _context;

    public EFCourseRepository(IUnitOfWork unitOfWork)
    {
        _context = (ObjectContext)unitOfWork;
    }

    public IEnumerable<Course> FindBy(string text)
    {
        var qry = from c in _context.CreateObjectSet<tblCourse>()
            where c.CourseName.Contains(text)
            select new Course()
            {
                Id = c.CourseId,
                Name = c.CourseName
            };
        return qry.AsEnumerable();
    }

    public Course Register(string courseName)
    {
        var c = new tblCourse()
        {
            CourseName = courseName;
        };
        _context.AddObject(c);
        //the repository needs to call SaveChanges to get the primary key of the newly created entry in tblCourse...
        var createdCourse = new Course()
        {
            Id = c.CourseId,
            Name = c.CourseName;
        };
        return createdCourse;
    }
}

public class EFUnitOfWork : ObjectContext, IUnitOfWork
{
    public EFUnitOfWork(string connectionString) : base(connectionString)
    {}

    public void Commit()
    {
        SaveChanges();
    }

    public object Context
    {
        get { return this; }
    }
}

上記のコメントでは、変更をコミットする必要があると感じる場所を確認できますが、サービスレイヤーとリポジトリ実装の両方でトランザクションのスコープを制御できるようにすることで、より大きな問題を見落としている可能性があります。

これに加えて、リポジトリが新しいオブジェクトを保存し、新しく指定された主キーをそのままにして返す必要がある場合、オブジェクトが返された後にUIからCommitを呼び出していると、これは発生しません。そのため、リポジトリは作業単位を管理する必要がある場合があります。

私のアプローチに差し迫った問題がありますか?

4

1 に答える 1

2

以上が作業単位の「境界」についてです。論理演算の境界は? UI - コード ビハインド / コントローラーまたはサービス レイヤーですか。境界とは、作業単位とは誰が定義するのですか? 複数のサービス呼び出しを単一の作業単位に振り分けるのは UI 開発者の責任ですか、それとも単一の作業単位をラップするサービス操作を公開するのはサービス開発者の責任ですか? これらの質問Commitにより、作業単位の を呼び出す場所がすぐにわかります。

論理操作の境界が UI 開発者によって定義されている場合、この方法では実行できません。UI 開発者は、メソッドを呼び出す前にコミットされていない変更を加えることができますが、検索をログに記録すると、これらの変更を黙ってコミットします。このような場合、ログ操作は独自のコンテキスト/作業単位を使用する必要があり (さらに、現在のトランザクションの外部で実行する必要があります)、呼び出しごとに新しい UoW インスタンスを作成する個別の ninject 構成が必要になります。論理操作の境界がサービス内にある場合、作業単位を UI 開発者に公開しないでください。UI 開発者は、アクティブな UoW インスタンスとやり取りできないはずです。

あなたの実装で私が気に入らないのは、作業単位をRegister呼び出すことです。Commitまた境界線はどこですか?リポジトリ操作は自己完結型の作業単位ですか? そのような場合、なぜサービス層があるのですか? 単一の作業単位で複数の curses を登録したい場合、またはコース登録をより大きな作業単位の一部にしたい場合はどうなりますか? を呼び出すのはサービス層の役割ですCommit。この全体はおそらく、リポジトリがエンティティをカスタム タイプ / DTO に射影するという考えから来ています。これは、リポジトリに対する責任が大きすぎて複雑すぎるように見えます。特に POCO (EFv4.x) を使用できる場合。

最後に言及すること - サービス操作を作業単位の境界として作成すると、リクエストごとのインスタンス化では不十分な状況を見つけることができます。内部で複数の作業単位を実行する Web 要求を使用できます。

ついに。UI 開発者の責任について懸念している - UI 開発者が実装について懸念しているのと同時に、UI 開発者が複数のサービス操作を並行して実行することを決定した場合 (EF コンテキストはスレッドセーフではありませんが、1 つしかありません)リクエスト処理全体に対して)? したがって、すべてはあなたと UI 開発者の間のコミュニケーション (または非常に優れたドキュメント) にかかっています。

于 2011-06-04T13:39:38.623 に答える