工作范围单位



我有一个解决方案,它使用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,则不会发生这种情况。因此,存储库有时确实需要管理工作单元。

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

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

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

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

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

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

最新更新