工作单位范围

时间:2011-06-04 12:38:19

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

我有一个使用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; }
    }
}

在上面的评论中,您可以看到我认为我应该提交更改的地方,但我觉得我可能通过允许服务层和存储库实现来控制事务范围来忽略更大的问题。

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

你能看到我方法的任何直接问题吗?

1 个答案:

答案 0 :(得分:2)

这就是你工作单位的“边界”。你的逻辑运算的边界是什么?是UI - 代码隐藏/控制器还是服务层?通过边界我的意思是谁定义什么是工作单位? UI开发人员是否有责任将多个服务调用编排为单个工作单元,或者服务开发人员是否有责任公开每个包含单个工作单元的服务操作?这些问题应该可以立即回答应该调用工作单元Commit的地方。

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

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

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

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