DbContext already implements unit of work internally -- and all this means is that when you call Save Changes it has a transaction for all of the activity performed against the same instance. So if you want to implement the UoW pattern on top of this, just
make sure any repositories you hand out from some unit of work factory all end up referring back to the same instance of the DbContext class.
What would be the reason for implementing UOW on top of DbContext if it already internally implements UOW?
Here is the structure I have now, just looking for advice as to whether or not this is following best practices. I am not posting all code for the project, just concentrating on Users and Roles for now. Here is some of the project structure
2. Is it OK if my repository knows about my UserEditViewModel?
No. ViewModels belong to the UI layer. The dependency should be the other way.
outhowz42
3. Should UnitOfWork be an IUnitOfWork instead that my DataContext implements?
Yes, again for testing. Your UnitOfWork class is concrete and tightly bound to the DbContext (and thus a "real" DB). You don't want the real DB involved while running unit tests. But other than that your UoW code looks spot on (given the brief glance I gave
it :).
outhowz42
4. Is it pointless to use repositories and IRepositiory? Should I just be using the DbSets in the DataContext class?
If you can mock them for testing, then you can. But since that tends to be a pain people tend to go with interfaces.
As a follow up to question 2. I agree that the repository shouldn't know about the ViewModel. However, where do I put that mapping logic then? Prior to moving it into the repository it was just in the controllers HttpPost Edit ActionResult. As far as I know,
the controllers shouldn't be littered with all of that logic so I need to move it somewhere but where?
Should there be another services layer that essentially maps the updated properties in the view model to the domain model, which then accesses the "naked" UpdateUser method in the repository?
public void UpdateUser(User user)
{
context.Entry(user).State = EntityState.Modified;
}
So the repository should be defined in terms of domain entities -- classes defined by the data layer. It's then up to the UI to map. You can do this in a controller's action, or you could do this in the view model itself. Also, I've seen extension methods
that do the mapping. There's no hard & fast rule, but you're right when you want to keep things clean.
Ok, so if I move the logic around I no longer have any reference to a ViewModel in the UserRepository. I now have a method in the UserEditViewModel to perform the updates. My HttpPost Edit ActionResult in the UserController has also been updated. Does this
seem acceptable?
//In UserEditViewModel I have this new method
public User UpdateDomainModelUser(User user, IEnumerable<Role> roles)
{
user.UserName = this.UserName;
user.Email = this.Email;
user.FirstName = this.FirstName;
user.LastName = this.LastName;
user.PhoneExtension = this.PhoneExtension;
user.IsApproved = this.IsApproved;
foreach (RoleViewModel rvm in this.RoleViewModels)
{
if (rvm.Checked)
{
//add role if it doesn't exist
var roleToAdd = (from role in roles
where role.RoleId.Equals(rvm.Id)
select role).First();
if (roleToAdd != null)
user.Roles.Add(roleToAdd);
}
else
{
//remove role
var roleToRemove = from role in roles
where role.RoleId.Equals(rvm.Id)
select role;
if (roleToRemove.Count() == 1)
user.Roles.Remove(roleToRemove.ElementAt(0));
}
}
return user;
}
//And here is the updated UserController ActionResult
[HttpPost]
public ActionResult Edit(UserEditViewModel userEditViewModel)
{
if (ModelState.IsValid)
{
User user = unitOfWork.UserRepository.GetUserByID(userEditViewModel.UserId);
userEditViewModel.UpdateDomainModelUser(user, unitOfWork.RoleRepository.GetRoles());
unitOfWork.Save();
return RedirectToAction("Index");
}
return View(unitOfWork.UserRepository.GetUserByID(userEditViewModel.UserId));
}
outhowz42
Member
80 Points
121 Posts
UnitOfWork pattern question
May 31, 2012 01:13 PM|LINK
Is using UnitOfWork and repositories overkill in an MVC 3 EF4 app since DbContext inherently supports the UnitOfWork concept?
I have a DataContext in my project like this:
public class DataContext : DbContext { #region Constructor public DataContext() : base("MyDatabaseName") { } #endregion #region Properties public DbSet<Department> Departments { get; set; } public DbSet<Role> Roles { get; set; } public DbSet<SampleRequest> SampleRequests { get; set; } public DbSet<User> Users { get; set; } #endregion #region Overrides protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<SampleRequest>() .HasRequired(s => s.RequestedBy) .WithMany(u => u.SamplesRequested) .HasForeignKey(s => s.RequestedById) .WillCascadeOnDelete(false); modelBuilder.Entity<SampleRequest>() .HasRequired(s => s.ApprovedBy) .WithMany(u => u.SampleRequestsApproved) .HasForeignKey(s => s.ApprovedById) .WillCascadeOnDelete(false); } #endregion }From what I gather, I should also have an IUnitOfWork that the DataContext implements.
public interface IUnitOfWork { void Begin(); void Commit(); void RollBack(); }How do I implement Begin and RollBack?
BrockAllen
All-Star
28072 Points
4996 Posts
MVP
Re: UnitOfWork pattern question
May 31, 2012 01:53 PM|LINK
DbContext already implements unit of work internally -- and all this means is that when you call Save Changes it has a transaction for all of the activity performed against the same instance. So if you want to implement the UoW pattern on top of this, just make sure any repositories you hand out from some unit of work factory all end up referring back to the same instance of the DbContext class.
DevelopMentor | http://www.develop.com
thinktecture | http://www.thinktecture.com/
outhowz42
Member
80 Points
121 Posts
Re: UnitOfWork pattern question
May 31, 2012 02:29 PM|LINK
What would be the reason for implementing UOW on top of DbContext if it already internally implements UOW?
Here is the structure I have now, just looking for advice as to whether or not this is following best practices. I am not posting all code for the project, just concentrating on Users and Roles for now. Here is some of the project structure
Folder >> Files:
Controllers >> AccountController, UsersController, RoleController
DAL >> DataContext, DataContextInitializer, IUserRepository, UserRepository, IRoleRepository, RoleRepository, UnitOfWork
Models >> AccountModels, User, Role
ViewModels >> RoleViewModel, UserEditViewModel
//LEAVING OUT MOST ACTION RESULT METHODS FOR BREVITY... public class UserController : Controller { private UnitOfWork unitOfWork = new UnitOfWork(); // // GET: /User/ public ActionResult Index() { return View(unitOfWork.UserRepository.GetUsers()); } // // GET: /User/Edit/5 public ActionResult Edit(Guid id) { User user = unitOfWork.UserRepository.GetUserByID(id); if (user == null) { return HttpNotFound(); } return View(new UserEditViewModel(user, unitOfWork.RoleRepository.GetRoles())); } // // POST: /User/Edit/5 [HttpPost] public ActionResult Edit(UserEditViewModel userEditViewModel) { if (ModelState.IsValid) { unitOfWork.UserRepository.UpdateUser(userEditViewModel); unitOfWork.Save(); return RedirectToAction("Index"); } return View(unitOfWork.UserRepository.GetUserByID(userEditViewModel.UserId)); } protected override void Dispose(bool disposing) { unitOfWork.Dispose(); base.Dispose(disposing); } } public interface IUserRepository : IDisposable { IEnumerable<User> GetUsers(); User GetUserByID(Guid userID); void InsertUser(User user); void DeleteUser(Guid userID); void UpdateUser(User user); void Save(); } public class UserRepository : IUserRepository, IDisposable { #region Member Fields private DataContext context; #endregion #region Constructor(s) public UserRepository(DataContext context) { this.context = context; } #endregion #region IUserRepository Members public IEnumerable<User> GetUsers() { return context.Users.OrderBy(u => u.LastName).OrderBy(u => u.FirstName).ToList(); } public User GetUserByID(Guid userID) { return context.Users.Find(userID); } public void InsertUser(User user) { context.Users.Add(user); } public void DeleteUser(Guid userID) { User user = context.Users.Find(userID); context.Users.Remove(user); } public void UpdateUser(User user) { context.Entry(user).State = EntityState.Modified; } public void UpdateUser(UserEditViewModel userEditViewModel) { User user = GetUserByID(userEditViewModel.UserId); user.UserName = userEditViewModel.UserName; user.Email = userEditViewModel.Email; user.FirstName = userEditViewModel.FirstName; user.LastName = userEditViewModel.LastName; user.PhoneExtension = userEditViewModel.PhoneExtension; user.IsApproved = userEditViewModel.IsApproved; foreach (RoleViewModel rvm in userEditViewModel.RoleViewModels) { if (rvm.Checked) { //add role if it doesn't exist var roleToAdd = (from role in context.Roles where role.RoleId.Equals(rvm.Id) select role).First(); if (roleToAdd != null) user.Roles.Add(roleToAdd); } else { //remove role var roles = from role in user.Roles where role.RoleId.Equals(rvm.Id) select role; if (roles.Count() == 1) user.Roles.Remove(roles.ElementAt(0)); } } context.Entry(user).State = EntityState.Modified; } public void Save() { context.SaveChanges(); } #endregion //disposal stuff removed for brevity }public class UnitOfWork : IDisposable { #region Member Fields private DataContext context = new DataContext(); #endregion #region Properties public RoleRepositoroy RoleRepository { get { if (_roleRepository == null) _roleRepository = new RoleRepositoroy(context); return _roleRepository; } }private RoleRepositoroy _roleRepository; public UserRepository UserRepository { get { if (_userRepository == null) _userRepository = new UserRepository(context); return _userRepository; } }private UserRepository _userRepository; #endregion #region Methods public void Save() { context.SaveChanges(); } #endregion #region IDisposable Members private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { context.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion }Now just a few questions.
1. In my UserController I declare a UnitOfWork. I believe I should be injecting this into the controller instead for testability. How can I do that?
2. Is it OK if my repository knows about my UserEditViewModel?
3. Should UnitOfWork be an IUnitOfWork instead that my DataContext implements?
4. Is it pointless to use repositories and IRepositiory? Should I just be using the DbSets in the DataContext class?
Thanks!!
BrockAllen
All-Star
28072 Points
4996 Posts
MVP
Re: UnitOfWork pattern question
May 31, 2012 03:18 PM|LINK
To abstract the data layer for testing.
http://bradwilson.typepad.com/blog/2010/10/service-location-pt5-idependencyresolver.html
No. ViewModels belong to the UI layer. The dependency should be the other way.
Yes, again for testing. Your UnitOfWork class is concrete and tightly bound to the DbContext (and thus a "real" DB). You don't want the real DB involved while running unit tests. But other than that your UoW code looks spot on (given the brief glance I gave it :).
If you can mock them for testing, then you can. But since that tends to be a pain people tend to go with interfaces.
DevelopMentor | http://www.develop.com
thinktecture | http://www.thinktecture.com/
outhowz42
Member
80 Points
121 Posts
Re: UnitOfWork pattern question
May 31, 2012 03:30 PM|LINK
Thanks for the feedback.
As a follow up to question 2. I agree that the repository shouldn't know about the ViewModel. However, where do I put that mapping logic then? Prior to moving it into the repository it was just in the controllers HttpPost Edit ActionResult. As far as I know, the controllers shouldn't be littered with all of that logic so I need to move it somewhere but where?
Should there be another services layer that essentially maps the updated properties in the view model to the domain model, which then accesses the "naked" UpdateUser method in the repository?
public void UpdateUser(User user) { context.Entry(user).State = EntityState.Modified; }BrockAllen
All-Star
28072 Points
4996 Posts
MVP
Re: UnitOfWork pattern question
May 31, 2012 03:41 PM|LINK
So the repository should be defined in terms of domain entities -- classes defined by the data layer. It's then up to the UI to map. You can do this in a controller's action, or you could do this in the view model itself. Also, I've seen extension methods that do the mapping. There's no hard & fast rule, but you're right when you want to keep things clean.
DevelopMentor | http://www.develop.com
thinktecture | http://www.thinktecture.com/
outhowz42
Member
80 Points
121 Posts
Re: UnitOfWork pattern question
May 31, 2012 03:53 PM|LINK
Ok, so if I move the logic around I no longer have any reference to a ViewModel in the UserRepository. I now have a method in the UserEditViewModel to perform the updates. My HttpPost Edit ActionResult in the UserController has also been updated. Does this seem acceptable?
//In UserEditViewModel I have this new method public User UpdateDomainModelUser(User user, IEnumerable<Role> roles) { user.UserName = this.UserName; user.Email = this.Email; user.FirstName = this.FirstName; user.LastName = this.LastName; user.PhoneExtension = this.PhoneExtension; user.IsApproved = this.IsApproved; foreach (RoleViewModel rvm in this.RoleViewModels) { if (rvm.Checked) { //add role if it doesn't exist var roleToAdd = (from role in roles where role.RoleId.Equals(rvm.Id) select role).First(); if (roleToAdd != null) user.Roles.Add(roleToAdd); } else { //remove role var roleToRemove = from role in roles where role.RoleId.Equals(rvm.Id) select role; if (roleToRemove.Count() == 1) user.Roles.Remove(roleToRemove.ElementAt(0)); } } return user; } //And here is the updated UserController ActionResult [HttpPost] public ActionResult Edit(UserEditViewModel userEditViewModel) { if (ModelState.IsValid) { User user = unitOfWork.UserRepository.GetUserByID(userEditViewModel.UserId); userEditViewModel.UpdateDomainModelUser(user, unitOfWork.RoleRepository.GetRoles()); unitOfWork.Save(); return RedirectToAction("Index"); } return View(unitOfWork.UserRepository.GetUserByID(userEditViewModel.UserId)); }BrockAllen
All-Star
28072 Points
4996 Posts
MVP
Re: UnitOfWork pattern question
May 31, 2012 04:30 PM|LINK
Again this is a gray area -- does it seem acceptable to you?
DevelopMentor | http://www.develop.com
thinktecture | http://www.thinktecture.com/
outhowz42
Member
80 Points
121 Posts
Re: UnitOfWork pattern question
May 31, 2012 05:05 PM|LINK
Yes but I'm a total noob with all of this stuff, thats why I'm looking for assurance.. from somewhere... lol.
BrockAllen
All-Star
28072 Points
4996 Posts
MVP
Re: UnitOfWork pattern question
May 31, 2012 05:17 PM|LINK
:)
Well, my point on the last question was that it looks fine to me and so hopefully you felt it was ok also.
DevelopMentor | http://www.develop.com
thinktecture | http://www.thinktecture.com/