简体   繁体   中英

How to Safely and Efficiently Insert/Update Records in Entity Framework 6 for Changed-Only Fields?

I am trying to safely Insert/Update an entity using Entity Framework 6. Instead of using the AddOrUpdate Method that is not Thread-Safe and not recommended for Production, I figured I would first attempt to insert the entity in my DB and if that fails due to a Primary Key collision, then I do an Update instead. I used the Database first model. My entity is a User whose Primary Key = UserID and all other fields are nullable. I need the Update Scenario to return the updated entity where the POST'ed values over the corresponding values leaving all others as is in the DB.

For example, my User entity has 20 properties. My POST may only update a small subset of these properties for a given UserID. I need the returned User to show the updated User.

For example, I first do a POST to insert a new User whose UserID = x1 like this:

{ "UserID": "x1", "FirstName": "User First Name", "LastName": "User Last Name", "Email": "email@company.com" }

My 2nd POST is an update for that User to update their email like this:

{ "UserID": "x1", "Email": "different_email@xyz.com" }

I need the 2nd POST to show me the original FirstName and LastName I set in the first Insert POST not NULL. I see the original FirstName and LastName in the DB but it does not show in the 2nd POST's response.

Questions are: (1) What am I doing wrong here? (2) I am making 2-3 queries to the DB. Is there a cleaner way to reduce the round trips to the DB without compromising on thread-safety and concurrency?

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

The second last line returns the original user object ... you should do:

return Ok(dbuser);

instead of:

return Ok(user);

Just to add a bit more color to this- remember that when you pass the user object to InsertUser or UpdateUser, you're essentially passing a copy of it. That is why you need to reload based on the UserID, and then return this refreshed user object.

Here is a slightly different approach. I haven't tested this code. Let us know if it doesn't work.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM