Architecture 工作范围单位

Architecture 工作范围单位,architecture,repository-pattern,ninject,unit-of-work,service-layer,Architecture,Repository Pattern,Ninject,Unit Of Work,Service Layer,我有一个解决方案,前端使用webforms,管理控制台使用mvc 这两个UI都通过Ninject使用服务层,我在解决一个微妙但相当重要的问题时遇到了困难 假设我有一个CourseService,它返回一个基于字符串搜索词的课程列表-该服务返回搜索结果,但出于管理信息的目的,我还需要记录所做的搜索以及与该词匹配的课程数量 我一开始的想法是,在请求结束时,工作单元将由UI在page方法中提交,例如button click事件。这同样适用于控制器 这里的问题是,我依靠UI开发人员在工作单元上调用Com

我有一个解决方案,前端使用webforms,管理控制台使用mvc

这两个UI都通过Ninject使用服务层,我在解决一个微妙但相当重要的问题时遇到了困难

假设我有一个CourseService,它返回一个基于字符串搜索词的课程列表-该服务返回搜索结果,但出于管理信息的目的,我还需要记录所做的搜索以及与该词匹配的课程数量

我一开始的想法是,在请求结束时,工作单元将由UI在page方法中提交,例如button click事件。这同样适用于控制器

这里的问题是,我依靠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; }
    }
}
公共类课程服务
{
专用只读ICourseRepository(U repo);
公共课程服务(ICO)
{
_回购=回购;
}
公共IEnumerable FindCoursesBy(字符串搜索术语)
{
var课程=_repo.FindBy(搜索术语);
var log=string.format(“搜索{1}”返回{0}个课程”,courses.Count(),searchTerm);
_报告日志搜索(日志);
//在我看来,服务层应该在这里对IUnitOfWork调用Commit()。。。
返回课程;
}
}
公共类EFCourseRepository:ICourseRepository
{
私有只读ObjectContext\u上下文;
公共EFCourseRepository(IUnitOfWork unitOfWork)
{
_上下文=(ObjectContext)unitOfWork;
}
公共IEnumerable FindBy(字符串文本)
{
var qry=来自_context.CreateObjectSet()中的c
其中c.CourseName.Contains(文本)
选择新课程()
{
Id=c.CourseId,
Name=c.CourseName
};
返回qry.AsEnumerable();
}
公共课程注册(字符串courseName)
{
var c=新的tblCourse()
{
CourseName=CourseName;
};
_AddObject(c);
//存储库需要调用SaveChanges来获取tblCourse中新创建的条目的主键。。。
var createdCourse=新课程()
{
Id=c.CourseId,
Name=c.CourseName;
};
返回createdCourse;
}
}
公共类EFUnitOfWork:ObjectContext,IUnitOfWork
{
公共EFUnitOfWork(字符串connectionString):基(connectionString)
{}
公共无效提交()
{
保存更改();
}
公共对象上下文
{
获取{返回此;}
}
}
在上面的评论中,您可以看到我认为我“应该”提交更改的地方,但我觉得我可能忽略了一个更大的问题,即允许服务层和存储库实现控制事务的范围

除此之外,当我的存储库需要保存一个新对象,并将其原封不动地返回时,如果在返回对象后从UI调用Commit,则不会发生这种情况。因此,存储库有时确实需要管理工作单元

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

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

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

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

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

最后。您关心UI开发人员的职责