I have 3 related tables in my database.
Farm ----> FarmCrops <----- Crops
I'm trying to update the a farm entity with a collection of crops but am running into problems. I've been working on this with no success for hours now so any help would be greatly appreciated.
The error I'm receiving is this:
The object cannot be attached because
it is already in the object context.
An object can only be reattached when
it is in an unchanged state.
My update logic is as follows (my apologies for the large amount of code. I'd just like to be as clear as possible):
bool isNew = false;
Farm farm;
// Insert or update logic.
if (viewModel.Farm.FarmId.Equals(Guid.Empty))
{
farm = new Farm
{
FarmId = Guid.NewGuid(),
RatingSum = 3,
RatingVotes = 1
};
isNew = true;
}
else
{
farm = this.ReadWriteSession
.Single<Farm>(x => x.FarmId == viewModel.Farm.FarmId);
}
// Edit/Add the properties.
farm.Name = viewModel.Farm.Name;
farm.Owner = viewModel.Farm.Owner;
farm.Address = viewModel.Farm.Address;
farm.City = viewModel.Farm.City;
farm.Zip = viewModel.Farm.Zip;
farm.WebAddress = viewModel.Farm.WebAddress;
farm.PhoneNumber = viewModel.Farm.PhoneNumber;
farm.Hostel = viewModel.Farm.Hostel;
farm.Details = viewModel.Farm.Details;
farm.Latitude = viewModel.Farm.Latitude;
farm.Longitude = viewModel.Farm.Longitude;
farm.Weather = viewModel.Farm.Weather;
// Add or update the crops.
string[] cropIds = Request.Form["crop-token-input"].Split(',');
List<Crop> allCrops = this.ReadWriteSession.All<Crop>().ToList();
if (!isNew)
{
// Remove all previous crop/farm relationships.
farm.Crops.Clear();
}
// Loop through and add any crops.
foreach (Crop crop in allCrops)
{
foreach (string id in cropIds)
{
Guid guid = Guid.Parse(id);
if (crop.CropId == guid)
{
farm.Crops.Add(crop);
}
}
}
if (isNew)
{
this.ReadWriteSession.Add<Farm>(farm);
}
else
{
this.ReadWriteSession.Update<Farm>(farm);
}
this.ReadWriteSession.CommitChanges();
My update code within the ReadWriteSession is simple enough (GetSetName<T> just returns the types name from it's PropertyInfo.):
/// <summary>
/// Updates an instance of the specified type.
/// </summary>
/// <param name="item">The instance of the given type to add.</param>
/// <typeparam name="T">The type of entity for which to provide the method.</typeparam>
public void Update<T>(T item) where T : class, new()
{
this.context.AttachTo(this.GetSetName<T>(), item);
this.context.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
}
You are adding existing Crop objects (from the allCrops list) to the new Farm. When you connect a new entity to an existing one, the new entity automatically gets attached to the context. Therefore you get the error when you try to attach the Farm to the context the second time.
The Add<Farm>(farm) statement in your code is not even necessary to connect the Farm to the context, and if you have an existing Farm that is loaded from the context, it is already attached to the context.
The whole of your if (isNew) statement is unnecessary. Entity framework tracks object state itself, so you don't need to set the modified state.
you don't have to attach the "farm" object at the end, because it's already attached as modified when you change one of its properties. try removing the else statement at the end:
if (isNew)
{
this.ReadWriteSession.Add<Farm>(farm);
}
Hope this helps :)
The problem is in your update method. You can't attach the Farm instance because you have loaded it from the same context so it is already attached and you don't need to call your Update at all because changes to attached objects are tracked automatically.
Related
I have a OData V4 over Asp.net WebApi (OWIN).
Everything works great, except when I try to query a 4-level $expand.
My query looks like:
http://domain/entity1($expand=entity2($expand=entity3($expand=entity4)))
I don't get any error, but the last expand isn't projected in my response.
More info:
I've set the MaxExpandDepth to 10.
All my Entities are EntitySets.
I'm using the ODataConventionModelBuilder.
I've opened an SQL-profiler and could see that the query (and the result) is correct. It's some filter that occurs after the query is executed.
I've searched the web and didn't find anything suitable.
I've tried different entity 4 level $expands and they didn't work as well.
Edit:
I've overridden the OnActionExecuted:
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
var objectContent = actionExecutedContext.Response.Content as ObjectContent;
var val = objectContent.Value;
var t = Type.GetType("System.Web.OData.Query.Expressions.SelectExpandWrapperConverter, System.Web.OData");
var jc = Activator.CreateInstance(t) as JsonConverter;
var jss = new JsonSerializerSettings();
jss.Converters.Add(jc);
var ser = JsonConvert.SerializeObject(val, jss);
}
The serialized value contains entity4.
I still have no idea what component removes entity4 in the pipe.
Edit #2:
I've create an adapter over DefaultODataSerializerProvider and over all the other ODataEdmTypeSerializer's. I see that during the process the $expand for entity4 exists and when the ODataResourceSerializer.CreateNavigationLink method is called on that navigationProperty (entity4) then it returns null.
I've jumped into the source code and I could see that the SerializerContext.Items doesn't include the entity4 inside it's items and the SerializerContext.NavigationSource is null.
To be specific with versions, I'm using System.Web.OData, Version=6.1.0.10907.
Ok, so I noticed the problem was due to the fact that my navigation property was of type EdmUnknownEntitySet and the navigation property lookup returns null (source code attached with an evil TODO..):
/// <summary>
/// Finds the entity set that a navigation property targets.
/// </summary>
/// <param name="property">The navigation property.</param>
/// <returns>The entity set that the navigation propertion targets, or null if no such entity set exists.</returns>
/// TODO: change null logic to using UnknownEntitySet
public override IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty property)
{
return null;
}
So I understood my problem was with the EdmUnknownEntitySet.
I digged into the code and saw that I needed to add the ContainedAttribute to the my navigation properties.
Since my solution is kind of a Generic repository, I've added it in the Startup for All navigation properties:
builder.OnModelCreating = mb => mb.StructuralTypes.SelectMany(s => s.NavigationProperties
.Where(np => np.Multiplicity == EdmMultiplicity.Many)).Distinct().ForEach(np => np.Contained());
//......
var model = builder.GetEdmModel();
I got update method that updates only the entity without its children
public void Update(T obj)
{
Ensure.IsNull(obj);
using (var DB = AccessManager.db)
{
DB.Set<T>().Attach(obj);
DB.Entry(obj).State = System.Data.EntityState.Modified;
DB.SaveChanges();
}
}
I try to update
LessonModel.Title = "Updated";
LessonModel.Files.Add(new LessonFileModel { IdxNumber = LessonModel.IdxNumber, FileName = "asd", FilePath = " asdf" });
DALFacade.Lesson.Update(LessonModel);
Only the title gets updated. The files are not updated.
So if the method like this:
public void Update(LessonModel obj)
{
Ensure.IsNull(obj);
using (var DB = AccessManager.db)
{
DB.Lessons.Attach(obj);
DB.Entry(obj).State = System.Data.EntityState.Modified;
DB.SaveChanges();
}
}
How can i save the child?
This is one of the problems that arise when you use the "Generic Repository" anti pattern. Writing an Update method that works for every combination of entity graphs that you pass in and will always do exactly what you want it to do is a big pain in the EF.
Instead, try writing repositories that match your use cases.
Your current problem could be solved if you added the files to the lesson after you attached it to the context. Marking an entry as modified marks all properties on this entry as modified, it doesn't affect relationships.
I'm hoping this will be an easy one but for the life of me, I cannot find the specific answer elsewhere on SO or any other site.
I have a basic repository/unit of work pattern with Entity Framework Code First. It all works smoothly except for certain cases of Update. THe problem is I have a set of Entity Framework model objects, all prefixed with "Db" which EF returns, but I then convert them to plain DataContract Model objects to pass to the Web layer to give separation of concerns. I have a basic conversion interface that just populates a WebModel object from the DataModel object, copying field by field verbatim.
So if you retrieve a DbUser object from EF with ID of 1, then convert to a User object, then convert that BACK to a DbUser object, you end up with a DbUser with ID of 1, but it is a DIFFERENT object to the one you started with, though they have the same primary key field, the actual CLR objects themselves are different.
The following works
User user;
using (var work = new UnitOfWork())
{
var repository = new UserDataRepository(work);
user = repository.Get(1);
repository.save();
}
var modelUser = DataConverter.Convert(user);
modelUser.Name = "new name";
user = BusinessConverter.Convert(modelUser);
using (var work = new UnitOfWork())
{
var repository = new UserDataRepository(work);
repository.Update(user);
repository.save();
}
As they are using two different unit of works/contexts, so the second block has nothing in the ObjectStateManager to compare to and can just attach the detached object in the Update() methods
This, however does NOT work
using (var work = new UnitOfWork())
{
var repository = new UserDataRepository(work);
user = repository.Get(1);
repository.save();
var modelUser = DataConverter.Convert(user);
modelUser.Name = "new name";
user = BusinessConverter.Convert(modelUser)
repository.Update(user);
repository.save();
}
NOTE: I know logically this doesn't make much sense to convert and just convert back but go with it, I've simplified the example greatly to make it easier to put into paper, in my actual code there is a reason for doing it that way.
I get the usual error "an object with the same key already exists in the objectstatemanager...". I'm assuming because the Get() loads the object into EF and then the update sees that the object is detached, then tries to attach it and it already exists.
My Update method in my repository is as below
public override bool UpdateItem(DbUser item)
{
if (Work.Context.Entry(item).State == EntityState.Detached)
Work.Context.Users.Attach(item);
Work.Context.Entry(item).State = EntityState.Modified;
return Work.Context.Entry(item).GetValidationResult().IsValid;
}
I made this Extension method to the DbContext to ReAttach the Entity without problems try it out:
public static void ReAttach<T>(this DbContext context, T entity) where T : class
{
var objContext = ((IObjectContextAdapter) context).ObjectContext;
var objSet = objContext.CreateObjectSet<T>();
var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entity);
Object foundEntity;
var exists = objContext.TryGetObjectByKey(entityKey, out foundEntity);
// Detach it here to prevent side-effects
if (exists)
{
objContext.Detach(foundEntity);
}
context.Set<T>().Attach(entity);
}
Then just update your method :
public override bool UpdateItem(DbUser item)
{
Work.Context.ReAttach(item);
Work.Context.Entry(item).State = EntityState.Modified;
return Work.Context.Entry(item).GetValidationResult().IsValid;
}
You might get a manged Entity, and again verbatim map the new DbUser's properties to the managed Object:
public override bool UpdateItem(DbUser item)
{
using (var work = new UnitOfWork())
{
var repository = new UserDataRepository(work);
DbUser managedUser = repository.Get(item.PK);
//foreach DbUser property map the item to managedUser
managedUser.field1 = item.field1;
[..]
repository.Update(managedUser);
repository.Save();
}
}
If you set your context to AsNoTracking() this will stop aspmvc tracking the changes to the entity in memory (which is what you want anyway on the web).
_dbContext.Products.AsNoTracking().Find(id);
I would recommend you read more about this at http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application
Yash
I'm trying to update an entity using a stub. This works fine for changing records, unless I try to set the value back to the default value forr the column. Eg: If the default value is 0, I can change to and from any value except zero, but the changes aren't saved if I try to set it back to zero. This is the code I'm using:
var package = new Package() {
PackageID = 4
};
...
public static void EditPackage(Package package) {
using(var context = new ShopEntities()) {
context.Packages.MergeOption = MergeOption.NoTracking;
var existing = new Package() {
PackageID = package.PackageID
};
context.AttachTo("Packages", existing);
context.ApplyPropertyChanges("ShopEntities.Packages", package);
context.AcceptAllChanges(); // doesn't make a difference
System.Diagnostics.Debug.WriteLine((package.DateSent.HasValue ? package.DateSent.Value.ToString("D") : "none") + "\t\t" + package.IsReceived);
context.SaveChanges();
}
}
In the example above, DateSent's default value is null (It's a DateTime?), and I can also set it to any value other than null, and the debug line confirms the correct properties are set, they're just not saved. I think I must be missing something.
Thanks for any help.
Turns out what I needed to do was manually mark each property in the new item as modified.
/// <summary>
/// Sets all properties on an object to modified.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="entity">The entity.</param>
private static void SetAllPropertiesModified(ObjectContext context, object entity) {
var stateEntry = context.ObjectStateManager.GetObjectStateEntry(entity);
// Retrieve all the property names of the entity
var propertyNames = stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(fm => fm.FieldType.Name);
foreach(var propertyName in propertyNames) {// Set each property as modified
stateEntry.SetModifiedProperty(propertyName);
}
}
You are creating a new package (with the id of an existing package), which you are calling "existing". You are then attaching it as if it was an existing package. You should load this package from the database and then attach it.
I'm creating a Dynamics CRM workflow assembly to be executed when a new Note is created on another record of any type. I need to be able to access a property Prop1 on that newly created Note entity to accomplish other tasks.
Previously I've only accessed values that were input from a field or from the user, but never on a property of a newly created entity. Any guidance would be appreciated.
UPDATE:
This is regarding CRM 4.0.
More information while I wait:
Ultimately, this workflow assembly will create an email that contains a link to the parent entity of the newly created Note record. The property I need to get is the AnnotationId. Once the Note record is created, I will be retrieving the ObjectId and ObjectTypeCode based on the AnnotationId of the newly created Note.
(In case you were curious)
Ok so if your using 4.0 custom workflows and not 3.0 callouts, you should add a workflow assembly, and use the Context service and executing context of your workflow to pull the values from the new note.
See the example below on how to access a record using the context service and the ID of your current context of execution (that should be your note)
/// <summary>
/// The Execute method is called by the workflow runtime to execute an activity.
/// </summary>
/// <param name="executionContext"> The context for the activity</param>
/// <returns></returns>
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
// Get the context service.
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext context = contextService.Context;
// Use the context service to create an instance of CrmService.
ICrmService crmService = context.CreateCrmService(true);
BusinessEntity newNote = GetNote(crmService, context.PrimaryEntityId);
string noteAttrib;
noteAttrib = newNote.Properties.Contains("AnnotationId") ? ((Lookup)newNote.Properties["annotationid"]).name.ToString() : null;
return ActivityExecutionStatus.Closed;
}
GetNotes method would be a standard query for notes by Id through a CRM service call,
here is an example slightly modified from MSDN to return a note:
private BusinessEntity getNote(ICrmService service, guid noteid)
{
// Create the column set object that indicates the fields to be retrieved.
ColumnSet cols = new ColumnSet();
// Set the columns to retrieve, you can use allColumns but its good practice to specify:
cols.Attributes = new string [] {"name"};
// Create the target object for the request.
TargetRetrieveAnnotation target = new TargetRetrieveAnnotation();
// Set the properties of the target object.
// EntityId is the GUID of the record being retrieved.
target.EntityId = noteid;
// Create the request object.
RetrieveRequest retrieve = new RetrieveRequest();
// Set the properties of the request object.
retrieve.Target = target;
retrieve.ColumnSet = cols;
// Execute the request.
RetrieveResponse retrieved = (RetrieveResponse)service.Execute(retrieve);
return RetrieveResponse;
}