【问题标题】:Unit of Work Scope工作范围单位
【发布时间】:2011-09-08 08:52:40
【问题描述】:

我有一个解决方案,将 webforms 用于前端,将 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; }
    }
}

在上面的 cmets 中,您可以看到我觉得我“应该”在哪里提交我的更改,但我觉得通过允许服务层和存储库实现来控制事务的范围,我可能忽略了一个更大的问题。

除此之外 - 当我的存储库需要保存一个新对象并完整返回新给定的主键时,如果我在对象返回后从 UI 调用 Commit,则不会发生这种情况。所以仓库有时确实需要管理工作单元。

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

【问题讨论】:

    标签: architecture repository-pattern ninject unit-of-work service-layer


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 感谢您的发人深省的回复。我的逻辑操作的边界不应该由 UI 开发人员定义,这就是促使我质疑当前方法的原因。相当一部分 UI 工作将由对业务领域知之甚少的初级开发人员完成,因此我们希望尽可能少地给他们提出质疑。服务层肯定是必需的,因为我们可能需要构建多个实现,因为我们可能需要构建多个实现,因为我们需要存储库层相当“愚蠢”,因为一些客户可能非常希望使用基于其他平台的存储解决方案。
    猜你喜欢
    • 1970-01-01
    • 2020-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多