C# 如何安全有效地在Entity Framework 6中为仅更改的字段插入/更新记录?

C# 如何安全有效地在Entity Framework 6中为仅更改的字段插入/更新记录?,c#,entity-framework,entity-framework-6,ef-database-first,C#,Entity Framework,Entity Framework 6,Ef Database First,我正在尝试使用EntityFramework 6安全地插入/更新实体。我想我应该先尝试在数据库中插入实体,如果由于主键冲突而失败,那么我会进行更新,而不是使用不安全且不推荐用于生产的AddOrUpdate方法。我使用了数据库第一个模型。我的实体是一个主键=UserID的用户,所有其他字段都可以为空。我需要更新场景来返回更新后的实体,其中POST'ed的值超过相应的值,剩下的所有其他值都在数据库中 例如,我的用户实体有20个属性。我的帖子可能只更新给定用户ID的这些属性的一小部分。我需要返回的用户

我正在尝试使用EntityFramework 6安全地插入/更新实体。我想我应该先尝试在数据库中插入实体,如果由于主键冲突而失败,那么我会进行更新,而不是使用不安全且不推荐用于生产的AddOrUpdate方法。我使用了数据库第一个模型。我的实体是一个主键=UserID的用户,所有其他字段都可以为空。我需要更新场景来返回更新后的实体,其中POST'ed的值超过相应的值,剩下的所有其他值都在数据库中

例如,我的用户实体有20个属性。我的帖子可能只更新给定用户ID的这些属性的一小部分。我需要返回的用户显示更新的用户

例如,我首先发布一篇文章,插入一个UserID=x1的新用户,如下所示:

{ “用户ID”:“x1”, “名字”:“用户名”, “姓氏”:“用户姓氏”, “电子邮件”:email@company.com" }

我的第二篇文章是该用户更新电子邮件的更新,如下所示:

{ “用户ID”:“x1”, “电子邮件”:“不同_email@xyz.com" }

我需要在第二篇文章中显示我在第一篇插入文章中设置的原始名字和姓氏NOTNULL。我在DB中看到了最初的名字和姓氏,但在第二篇帖子的回复中没有显示

问题是: (1) 我做错了什么? (2) 我正在向DB询问2-3个问题。是否有一种更干净的方法可以减少到DB的往返,而不影响线程安全性和并发性

    private UCBContext db = new UCBContext();
    private bool InsertUser(ref User user)
    {
        try
        {
            db.Users.Add(user);
            db.SaveChanges();
            return (true);
        }
        catch { }
        return (false);
    }

    private bool UpdateUser(ref User user)
    {
        try
        {
            db.Users.Attach(user);
            DbEntityEntry entry = db.Entry(user);

            foreach (var propertyName in entry.CurrentValues.PropertyNames)
            {
                var value = entry.CurrentValues[propertyName];
                entry.Property(propertyName).IsModified = (propertyName != "UserID" && value != null);
            }

            db.SaveChanges();
            return (true);
        }
        catch { }
        return (false);
    }

    // POST: api/users = Insert/Update User
    [ResponseType(typeof(User))]
    public IHttpActionResult PostUser(User user)
    {
        if (!ModelState.IsValid){ return BadRequest(ModelState); }

        bool ok = InsertUser(ref user);
        if (!ok) { ok = UpdateUser(ref user); }

        User dbuser = db.Users.Find(user.UserID);
        if(dbuser == null) { return NotFound(); }

        return Ok(dbuser);
    }

public partial class User
{
    public User()
    {
        this.Logins = 0;
    }

    public string UserID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public Nullable<int> Logins { get; set; }
}
private UCBContext db=new UCBContext();
专用布尔插入器(参考用户)
{
尝试
{
db.Users.Add(用户);
db.SaveChanges();
返回(真);
}
捕获{}
返回(假);
}
私有布尔更新用户(ref用户)
{
尝试
{
db.Users.Attach(用户);
DbEntityEntry=db.entry(用户);
foreach(entry.CurrentValues.PropertyNames中的var propertyName)
{
var value=entry.CurrentValues[propertyName];
entry.Property(propertyName).IsModified=(propertyName!=“UserID”&&value!=null);
}
db.SaveChanges();
返回(真);
}
捕获{}
返回(假);
}
//POST:api/users=插入/更新用户
[响应类型(用户类型))]
公共IHttpActionResult姿势器(用户)
{
如果(!ModelState.IsValid){return BadRequest(ModelState);}
bool ok=插入器(参考用户);
如果(!ok){ok=UpdateUser(ref-user);}
User dbuser=db.Users.Find(User.UserID);
如果(dbuser==null){return NotFound();}
返回Ok(dbuser);
}
公共部分类用户
{
公共用户()
{
this.Logins=0;
}
公共字符串用户标识{get;set;}
公共字符串名{get;set;}
公共字符串LastName{get;set;}
公共字符串电子邮件{get;set;}
可为空的公共登录名{get;set;}
}

最后第二行返回原始用户对象。。。你应该做:

return Ok(dbuser);
而不是:

return Ok(user);

只是想给它添加一点颜色——请记住,当您将用户对象传递给InsertUser或UpdateUser时,实际上是传递了它的一个副本。这就是为什么需要基于UserID重新加载,然后返回这个刷新的用户对象。

这里有一个稍微不同的方法。我还没有测试过这段代码。如果不起作用,请告诉我们

    try
    {
        var existingUser = db.Users.Find(user.UserId); //or use db.Users.FirstOrDefault(...)
        if(existingUser == null)
        {
            return false;
        }                 

        Type nType = user.GetType();
        PropertyInfo[] newValues = nType.GetProperties();

        foreach (PropertyInfo prop in newValues)
        {
            var propVal = prop.GetValue(user,null);
            if(propVal!= null)
            {
                var eProp = existingUser.GetType().GetProperty(prop.Name);
                if(eProp != null)
                {
                   eProp.SetValue(existingUser, propVal, null);
                }
            }               
        }        
        db.SaveChanges();
        return (true);
    }
    catch { }
    return (false);

我增加了else条款,但它没有解决问题。有什么想法吗?您是否尝试过在
if(value!=null)
处添加断点,查看该值是否实际为
null
或其他内容?请不要使用if语句。。。它可以简化为
entry.Property(propertyName).IsModified=value!=无效
@KosalaW:我调试了代码,不得不在foreach循环中显式跳过UserID。在我的原始帖子中更新了我的代码。它仍然不起作用。答案已更新。我还没有测试代码。试试看。我用你建议的代码更新了我的帖子。这并没有解决问题。有什么想法吗?id
db
来自哪里?@ErikPhilips:我跳过了db的定义。为了完整性,在上面添加了它。有什么想法吗?你能分享你的模型类吗?UserID是唯一的标识符吗?是的,UserID是主键。所有其他字段都可以为空。今天晚些时候,我将把User类添加到我的问题中。