2

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

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

回答

2

这就是你的工作单元的“边界”。你的逻辑运算的边界是什么?是UI代码/控制器还是服务层?我的意思是界定谁是工作单位? UI开发人员是否有责任将多个服务调用编排为单个工作单元,还是由服务开发人员负责公开每个包装单个工作单元的服务操作?这些问题应该在应该调用工作单元的Commit时立即给出答案。

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

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

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

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

+0

感谢这样一个发人深省的答复。我的逻辑操作的边界不应该由UI开发人员定义,这促使我质疑当前的方法。大部分UI工作将由初级开发人员完成,对业务领域知之甚少,所以我们希望尽可能少地提出问题。由于我们可能需要构建多个实现,因为我们需要存储库层非常“愚蠢”,因为某些客户可能非常希望使用基于其他平台的存储解决方案,因此服务层是必需的。 – Baldy 2011-06-04 16:21:46

相关问题