工作范围单元

发布于 2024-11-13 03:48:19 字数 2496 浏览 3 评论 0原文

我有一个使用网络表单作为前端的解决方案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,则不会发生这种情况。所以存储库有时确实需要管理工作单元。

您能看出我的方法有什么直接问题吗?

I have a solution that uses webforms for front end & mvc for admin console.

Both UIs consume a service layer via Ninject, and i am having trouble working out a subtle but rather important issue.

Suppose i have a CourseService that returns a list of courses based upon a string search term - the service returns the search results, but i also need to record the search that was made, and how many courses matched that term, for management information purposes.

I started out with the idea that the unit of work would be committed by the UI, at the end of the request, in the page method, such as the button click event. The same would apply for a controller.

The problem here is that i am relying on the UI developer to call Commit() on the Unit of Work in order for the search to be logged. The UI developer could happily carry on without calling commit and the results would be returned - but the search would not be logged. This leads me to the decision of letting the service layer control the scope of the unit of work. Ninject will automatically pass in the unit of work to both the service layer and the repository implementation, and this will effectively be the same instance as i have told ninject to create it per request scope.

Here is an example of how my layers are written...

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; }
    }
}

In the comments above you can see where i feel that i 'should' be committing my changes, but i feel i may be overlooking a bigger issue by allowing both the service layer AND the repository implementation to control the scope of transactions.

Further to this - when my repository needs to save a new object, and return it with the newly given primary key intact, this will not happen if i am calling Commit from the UI after the object has been returned. So the repository does need to manage the unit of work sometimes.

Can you see any immediate issues with my approach?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

找个人就嫁了吧 2024-11-20 03:48:19

这就是您工作单元的“边界”。你的逻辑运算的边界是什么?是 UI - 代码隐藏/控制器还是服务层?我所说的边界是指谁定义了什么是工作单元? UI 开发人员有责任编排对单个工作单元的多个服务调用,还是服务开发人员有责任公开每个包装单个工作单元的服务操作?这些问题应该可以立即为您提供答案,其中应该在工作单元上调用 Commit

如果逻辑操作的边界是由 UI 开发人员定义的,那么您就不能这样做 - 永远不会。 UI 开发人员可以在调用您的方法之前进行一些未提交的更改,但一旦您记录搜索,您将默默地提交这些更改!在这种情况下,您的 Log 操作必须使用自己的上下文/工作单元(而且它应该在当前事务之外运行),这将需要单独的 ninject 配置为每个调用创建新的 UoW 实例。如果逻辑操作的边界在服务中,则不应向 UI 开发人员公开工作单元 - 他不应该能够与活动的 UoW 实例进行交互。

我不喜欢您的实现是 Register 在工作单元上调用 Commit 。再说一遍,边界在哪里?存储库操作是独立的工作单元吗?在这种情况下为什么还要有服务层呢?如果您想在单个工作单元中注册多个诅咒,或者希望课程注册成为更大工作单元的一部分,会发生什么情况?服务层负责调用Commit。这整个可能来自于这样的想法:您的存储库将把实体投影到自定义类型/DTO 中 - 它看起来对存储库承担了太多的责任并且太复杂了。特别是如果您可以使用 POCO (EFv4.x)。

最后要提到的是,一旦您将服务操作作为工作单元的边界,您就会发现每个请求实例化是不够的。您可以拥有将在内部执行多个工作单元的网络请求。

最后。您关心 UI 开发人员的职责 - 同时 UI 开发人员可以关心您的实现 - 如果 UI 开发人员决定并行运行多个服务操作会发生什么(EF 上下文不是线程安全的,但您只有一个)对于整个请求处理)?因此,这一切都与您和 UI 开发人员之间的沟通有关(或与非常好的文档有关)。

That is all about "Boundary" of your unit of work. What is a boundary of your logical operation? Is it UI - code behind / controller or service layer? By boundary I mean who defines what is unit of work? Is it responsibility of an UI developer to choreograph multiple service calls to a single unit of work or is it responsibility of a service developer to expose service operations each wrapping a single unit of work? These questions should give you immediate answer where the Commit on a unit of work should be called.

If the boundary of your logical operations is defined by the UI developer you cannot do it this way - never. The UI developer can make some uncommitted changes before he calls your method but you will silently commit these changes once you log the search! In such case your Log operation must use its own context / unit of work (and moreover it should run outside of the current transaction) which would require separate ninject configuration creating new UoW instance for an each call. If the boundary of your logical operations is in the service you should not expose a unit of work to the UI developer - he should not be able to interact with your active UoW instance.

What I don't like in your implementation is Register calling Commit on unit of work. Again where is the boundary? Is the repository operation self contained unit of work? In such case why do you have a service layer? What happens if you want to register multiple curses in a single unit of work or if you want the course registration to be a part of a larger unit of work? It is responsibility of the service layer to call Commit. This whole probably comes from the idea that your repository will make projections of entities into custom types / DTOs - it looks like too much responsibility for the repository and too much complexity. Especially if you can use POCOs (EFv4.x).

The last thing to mention - once you make a service operation as a boundary for unit of work you can find a situation where per request instancing is not enough. You can have web requests which will internally execute multiple unit of works.

Finally. You are concerned about responsibilities of the UI developer - in the same time the UI developer can be concerned about your implementation - what happens if the UI developer decides to run multiple service operations in parallel (EF context is not thread safe but you have only one for the whole request processing)? So, it is all about communication between you and UI developer (or all about very good documentation).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文