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 User from the view, to the service to the repository, it sends up the user with a null or empty password string. This null gets written to the database (which I dont want).
How do I handle a situation like this?
Domain
public class User
{
public int UserId { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
Repository
public User Save(User user)
{
if (user.UserId > 0)
{
User dbUser = context.Users.FirstOrDefault(u => u.UserId == user.UserId);
//What do I do here?
}
context.Users.AddObject(user);
context.SaveChanges();
return user;
}
Lets say in this case, my view allows me to change only Email, so the only thing that gets sent back to the Save() method are: user.UserId and user.Email while user.Password is null. In my case, the database throws error because Password should be nullable.
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.
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;
}
来源:https://stackoverflow.com/questions/4697203/pure-poco-entity-update-problem-in-repository-pattern