I have a problem in my UserRepository in which I want to update a user. I dont want certain fields updated, such as password, unless specified. For example, When I pass the
You should use view models. View models are classes which are specifically tailored to the needs of a view and contain only the properties needed by this given view. So your controller action should look like this:
[HttpPost]
public ActionResult Update(UserViewModel model) { ... }
instead of:
[HttpPost]
public ActionResult Update(User model) { ... }
Inside the controller action you could map between the view model and the model. AutoMapper is a great tool that could simplify this task.
You should really be very careful and never expose your models like this. Always use view models to and from a view. Just imagine if there was an IsAdministrator
boolean property on your model.
Can you do this?
public User Save(User user)
{
if (user.UserId > 0)
{
User dbUser = context.Users.FirstOrDefault(u => u.UserId == user.UserId);
//What do I do here?
dbUser.Email = user.Email
user = dbUser;
}
else
{
context.Users.AddObject(user);
}
context.SaveChanges();
return user;
}
Detached POCO scenario (you will not load user from DB before update):
You can selectively say which properties must be updated:
public User Save(User user)
{
if (user.UserId == 0)
{
context.Users.AddObject(user);
}
else
{
context.Users.Attach(user);
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(user);
entry.SetModifiedProperty("Email");
}
context.SaveChanges();
return user;
}
You can also create two overloads of you Save
method. First will update whole object, second will update only explicitly selected properties:
public User Save(User user)
{
if (user.UserId == 0)
{
context.Users.AddObject(user);
}
else
{
context.Users.Attach(user);
context.ObjectStateManager.ChangeObjectState(user, EntityState.Modified);
}
context.SaveChanges();
return user;
}
public User Save(User user, IEnumerable<Expression<Func<User, object>>> properties)
{
if (user.UserId == 0)
{
context.Users.AddObject(user);
}
else
{
context.Users.Attach(user);
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(user);
foreach(var selector in properties)
{
string propertyName = PropertyToString(selector.Body);
entry.SetModifiedProperty(propertyName);
}
}
context.SaveChanges();
return user;
}
// Doesn't work for navigation properties!
private static string PropertyToString(Expression selector)
{
if (selector.NodeType == ExpressionType.MemberAccess)
{
return ((selector as MemberExpression).Member as PropertyInfo).Name;
}
throw new InvalidOperationException();
}
You will call the second overload this way:
userRepository.Save(user, new List<Expression<Func<User, object>>>
{
u => u.Email
});
Attached scenario (you will load user from DB before update):
You can modify your Save method to accept delegate so that you can control how update will be performed:
public User Save(User user, Action<User, User> updateStrategy)
{
if (user.UserId > 0)
{
User dbUser = context.Users.FirstOrDefault(u => u.UserId == user.UserId);
updateStrategy(dbUser, user);
}
else
{
// New object - all properties should be saved
context.Users.AddObject(user);
}
context.SaveChanges();
return user;
}
You will call the method this way:
var user = GetUpdatedUserFromSomewhere();
repository.Save(user, (dbUser, mergedUser) =>
{
dbUser.Email = mergedUser.Email;
});
Anyway, despite of my examples you should definitely think about Darin's post and special ModelViews for updating.