仓储模式中POCO实体更新问题的解决方案

4

我在UserRepository中遇到了一个问题,我想要更新用户。除非特别指定,否则我不希望更新某些字段,比如密码。例如,当我将用户从视图传递到服务再到存储库时,它会发送一个具有空密码字符串的用户。这个空值会被写入数据库(我不想要这样)。

我该如何处理这种情况?

领域

public class User
{
    public int UserId { get; set; }

    public string Email { get; set; }
    public string Password { get; set; }
}

仓库

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

假设在这种情况下,我的视图只允许我更改“电子邮件”,所以只有“用户ID”和“电子邮件”被发送回“保存()”方法,而“密码”为null。在我的情况下,数据库会抛出错误,因为密码应该是可空的。
3个回答

10

分离的POCO场景(在更新之前不会从数据库中加载用户):

您可以选择性地指定要更新的属性:

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

你也可以创建两个重载版本的 Save 方法。第一个将更新整个对象,第二个将仅更新显式选择的属性:
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();
}

您将以以下方式调用第二个重载函数:
userRepository.Save(user, new List<Expression<Func<User, object>>> 
    { 
        u => u.Email 
    });

附加场景(在更新之前,您将从数据库中加载用户):

您可以修改保存方法以接受委托,以便您可以控制如何执行更新:

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

你可以这样调用该方法:
var user = GetUpdatedUserFromSomewhere();
repository.Save(user, (dbUser, mergedUser) => 
    {
        dbUser.Email = mergedUser.Email;
    });

无论如何,尽管我提供了一些例子,但你一定要考虑Darin的帖子和用于更新的特殊ModelViews。

为什么对于那些显式更新对象的方法,PropertyToString 对于布尔值无效? - Shawn Mclean
那个updateStrategy真是太美妙了。太喜欢它了!我现在正在使用它来更新我的存储库的方法;调用代码可以说出要更新什么,以及如何更新它。 - Jez

1

你应该使用视图模型。视图模型是专门为视图需求量身定制的类,仅包含此给定视图所需的属性。因此,你的控制器操作应该如下所示:

[HttpPost]
public ActionResult Update(UserViewModel model) { ... }

改为:

[HttpPost]
public ActionResult Update(User model) { ... }

在控制器操作中,您可以在视图模型和模型之间进行映射。AutoMapper 是一个很棒的工具,可以简化这个任务。

您应该非常小心,永远不要像这样公开您的模型。始终使用视图模型从视图中获取数据。想象一下,如果您的模型上有一个 IsAdministrator 布尔属性会怎样。


1
这就是我所做的。这并没有解决存储库试图将密码字段更新为null的问题。 - Shawn Mclean
这是很好的建议,但它并没有展示如何处理存储库中特定字段的更新。 - Shawn Mclean
我点赞了这个答案(此时将其带回0),因为我认为这是正确的。在您的操作中,您可以从存储库中提取用户,仅应用所需的更改,然后再次持久化它。由于您正在执行的操作仅旨在更新电子邮件地址,因此您知道只需要更新该地址。 - Brian Ball
@Brian Ball,但这仍然与存储库如何保存数据无关,因为将UserViewModel映射到User仍然会使User.Password为空。 - Shawn Mclean
您可以使用视图模型获取需要编辑的字段。然后从数据库中检索对象,并仅更新在视图模型中公开的字段。由于您从数据库中检索了对象,因此当您通过存储库保存对象时,密码字段将被填充并正确保存。 - Brownman98

0

你能做到这个吗?

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

我猜你也可以使用if(!string.IsNullOrEmpty(user.Email)), if(!string.IsNullOrEmpty(user.Password))等来检查你想要更新的字段。 - Simon Hazelton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接