When using Attach in EF, is it possible to not ove

2019-07-06 17:14发布

问题:

I'm updating an existing entity by attaching it to my data context like this:

    var updatedDocumentState = new AccDocumentState()
    {
        Id = accDocumentState.Id,
        IsDocumentary = accDocumentState.IsDocumentary,
        IsEditable = accDocumentState.IsEditable,
        IsRecursive = accDocumentState.IsRecursive,
        Title = accDocumentState.Title,
       Reportable = accDocumentState.Reportable,

    };
        context.AccDocumentStates.Attach(updatedDocumentState);
        context.ObjectStateManager.ChangeObjectState(updatedDocumentState, System.Data.EntityState.Modified);
        flag = context.SaveChanges() > 0;

And this works, however after saving the attached entity, the properties of the existing entity which i didn't update, but i want to keep as they were, are overwritten and given null values. How can I attach my entity and keep the properties of the existing entity which i have not updated?

回答1:

EF has an Object Data change tracker. Is enabled via proxies Tracking changes in Poco entries

Essentially You/find Read the Object/Poco entity first. Change only those properties you want. And save. Only the changed properties are updated.

If you are not using autoDetectChnages

 this.Configuration.AutoDetectChangesEnabled = false; ////<<<<<<<<< Default true

Then you would Call Detect Changes before Saving.

But either way the concept is based around a Read first to get entity. Make the necessary changes and save.

Only the actually changes are sent back to Db. eg:

  var mypoco = Context.Set<TPoco>.Find(1);
  myPoco.propertyXyz = "changed";
  // normally not required by default, But incase your are not using tracking proxies , tell ef heads Up
  // Context.Context.ChangeTracker.DetectChanges(); // uncomment when needed
  Context.SaveChanged();

Only the actually changes are sent to DB.

Whilst the POST from Rameez is correct, it does not indicate why setting the whole entry as changed is desirable nor why do that ? Why link the State entry post from documentation ?

   Context.Entry(poco).State = state;  // why do this ? or the objectContext equivalent 

This will result in an UPdate Set for all values going to Database on SaveChanges Since ALL fields will be treated as changed. This is NOT a good way to use EF.

It is important to know about the auto detect changes in EF. See Automatic detect changes and Entity states and SaveChanges



回答2:

As per msdn When you change the EntityState of an entity object entry to Modified, all the properties of the object are marked as modified, regardless of the current or original values. http://msdn.microsoft.com/en-us/library/system.data.objects.objectstatemanager.changeobjectstate.aspx

Hence I think all other properties get set to null as the object that you create will have other properties as null or their default values. Below is the modified code.

 var updatedDocumentState = context.AccDocumentStates.First(a => a.Id== accDocumentState.Id);
            updatedDocumentState.IsDocumentary = accDocumentState.IsDocumentary,
            updatedDocumentState.IsEditable = accDocumentState.IsEditable,
            updatedDocumentState.IsRecursive = accDocumentState.IsRecursive,
            updatedDocumentState.Title = accDocumentState.Title,
            updatedDocumentState.Reportable = accDocumentState.Reportable,
            flag = context.SaveChanges() > 0;


回答3:

As a workaround to your problem, create a model for just the fields you are updating. Assuming this is a common scenario and warrants the extra model to avoid an extra call to the db.

With the new, minimized model, pointing to the same table, but with only the required properties, it will work as you want. Of course, nothing changed on the EF side, but it will only update the properties it knows about.

While I agree this is not how EF was designed, I too feel frustrated with the extra DB calls to do an update or delete. This solution helps with that.



回答4:

Try this. Maybe works as you need:

var updatedDocumentState = context.AccDocumentStates.Find(accDocumentState.Id)
{
    IsDocumentary = accDocumentState.IsDocumentary,
    IsEditable = accDocumentState.IsEditable,
    IsRecursive = accDocumentState.IsRecursive,
    Title = accDocumentState.Title,
    Reportable = accDocumentState.Reportable,
};

flag = context.SaveChanges() > 0;


回答5:

I've had luck with the following. First I created an extension method to unset the IsModified flag for any property that is not in the set of properties that I want to restrict updates to:

public static void RestrictModifiedProps<ENT>(this DbContext context, ENT entity, IEnumerable<string> restrictedPropNames)
  where ENT : class
{

  //Grab the meta entry that knows whether the entity/properties have been updated
  var entry = context.Entry(entity);
  if (entry == null) return;

  //loop over properties, only allow properties in the 
  //  restrictedPropNames list to be modified
  foreach (var propName in entry.CurrentValues.PropertyNames)
  {
    var prop = entry.Property(propName);
    if (!prop.IsModified) continue;

    prop.IsModified = restrictedPropNames.Any(O => O == propName);
  }
}

In my case, I am accepting the entity's property values from a json post to an MVC action. So, I want to find out what properties were posted and created a (couple) extension methods for the controller:

public static JObject JsonPostData(this Controller cntrlr)
{
  //ensure we're at the start of the input stream
  Stream req = cntrlr.Request.InputStream;
  req.Seek(0, SeekOrigin.Begin);

  //read in any potential json
  string json = d2s.SafeTrim(new StreamReader(req).ReadToEnd());
  if (string.IsNullOrWhiteSpace(json)
    || !json.StartsWith("{")
    || !json.EndsWith("}"))
    return null;

  //try to deserialize it
  return JsonConvert.DeserializeObject(json) as JObject;
}

public static IEnumerable<JProperty> JsonPostProperties(this Controller cntrlr)
{
  JObject jObj = cntrlr.JsonPostData();
  if (jObj == null) return null;


  return jObj.Properties();
}

public static IEnumerable<string> JsonPostPropNames(this Controller cntrlr)
{
  IEnumerable<JProperty> jProps = cntrlr.JsonPostProperties();
  if (jProps == null) return null;

  return jProps.Select(O => O.Name);
}

In the action, we get:

[HttpPost, ActionName("Edit")]
public virtual ActionResult Edit_Post(ENT obj)
{

  ...code...

  Ctxt.Set<ENT>().Attach(obj);
  Ctxt.Entry(obj).State = EntityState.Modified;
  Ctxt.RestrictModifiedProps(obj, this.JsonPostPropNames());

  ...code...

}


回答6:

If you are just excluding one or two properties, like say you never wanted to allow updates to the Title property (in your example), just unset IsModified on target properties after you set the object state to modified:

context.AccDocumentStates.Attach(updatedDocumentState);
context.ObjectStateManager.ChangeObjectState(updatedDocumentState, System.Data.EntityState.Modified);
context.Entry(updatedDocumentState).Property("Title").IsModified = false;
flag = context.SaveChanges() > 0;

Also FYI - Default MVC5 projects in VS use this line to set the object's modified property:

context.Entry(updatedDocumentState).State = System.Data.EntityState.Modified;