开发者

Unit of Work Scope

开发者 https://www.devze.com 2023-03-10 04:13 出处:网络
I have a solution that uses webforms for front end & mvc for admin console. Both UIs consume a service layer via Ninject, and i am having trouble working out a subtle but rather important issue.

I have a solution that uses webforms for front end & mvc for admin console.

Both UIs consume a service layer via Ninject, and i am having trouble working out a subtle but rather important issue.

Suppose i have a CourseService that returns a list of courses based upon a string search term - the service returns the search results, but i also need to record the search that was made, and how many courses matched that term, for management information purposes.

I started out with the idea that the unit of work would be committed by the UI, at the end of the request, in the page method, such as the button click event. The same would apply for a controller.

The problem here is that i am relying on the UI developer to call Commit() on the Unit of Work in order for the search to be logged. The UI developer could happily carry on without calling commit and the results would be returned - but the search would not be logged. This leads me to the decision of letting the service layer control the scope of the unit of work. Ninject will automatically pass in the unit of work to both the service layer and the repository implementation, and this will effectively be the same instance as i have told ninject to create it per request scope.

Here is an example of how my layers are written...

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; }
    }
}

In the comments above you can see where i feel that i 'should' be committing my changes, but i feel i may be overlooking a bigger issue by allowing both the service layer AND the repos开发者_StackOverflow中文版itory implementation to control the scope of transactions.

Further to this - when my repository needs to save a new object, and return it with the newly given primary key intact, this will not happen if i am calling Commit from the UI after the object has been returned. So the repository does need to manage the unit of work sometimes.

Can you see any immediate issues with my approach?


That is all about "Boundary" of your unit of work. What is a boundary of your logical operation? Is it UI - code behind / controller or service layer? By boundary I mean who defines what is unit of work? Is it responsibility of an UI developer to choreograph multiple service calls to a single unit of work or is it responsibility of a service developer to expose service operations each wrapping a single unit of work? These questions should give you immediate answer where the Commit on a unit of work should be called.

If the boundary of your logical operations is defined by the UI developer you cannot do it this way - never. The UI developer can make some uncommitted changes before he calls your method but you will silently commit these changes once you log the search! In such case your Log operation must use its own context / unit of work (and moreover it should run outside of the current transaction) which would require separate ninject configuration creating new UoW instance for an each call. If the boundary of your logical operations is in the service you should not expose a unit of work to the UI developer - he should not be able to interact with your active UoW instance.

What I don't like in your implementation is Register calling Commit on unit of work. Again where is the boundary? Is the repository operation self contained unit of work? In such case why do you have a service layer? What happens if you want to register multiple curses in a single unit of work or if you want the course registration to be a part of a larger unit of work? It is responsibility of the service layer to call Commit. This whole probably comes from the idea that your repository will make projections of entities into custom types / DTOs - it looks like too much responsibility for the repository and too much complexity. Especially if you can use POCOs (EFv4.x).

The last thing to mention - once you make a service operation as a boundary for unit of work you can find a situation where per request instancing is not enough. You can have web requests which will internally execute multiple unit of works.

Finally. You are concerned about responsibilities of the UI developer - in the same time the UI developer can be concerned about your implementation - what happens if the UI developer decides to run multiple service operations in parallel (EF context is not thread safe but you have only one for the whole request processing)? So, it is all about communication between you and UI developer (or all about very good documentation).

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号