Entity Framework 4 merge data to tracked entity - c#

I'm struggling with Entity Framework code first and merging.
I have an MVC controller with a generic repository. A view model gets posted up and I convert that into the type that EF knows about
var converted = AutoMapper.Mapper.Map<RoutineViewModel, Routine>(result);
_routineRepository.Update(converted);
In the repository I have:
/*
Routines.Attach(item);
ChangeTracker.Entries<Routine>().Single(x => x.Entity.Id == item.Id).State = EntityState.Modified;*/
var match = Routines.Single(x => x.Id == item.Id);
var entity = Entry(match);
entity.CurrentValues.SetValues(item);
I commented out the first bit because it was throwing an error about already tracking the entity even though a check like this:
if (ChangeTracker.Entries<Routine>().Count(x => x.Entity.Id == item.Id) != 0)
returned false
The problem I'm having is that the Routine object has an ICollection property of Steps. When I set the values of the tracked entity to match that of the poco the ICollection changes aren't propagated down. Looking around this site there looks to be a few nasty looking recursive calls. Is this really how it works or am I missing something?
Is there any easy way to say, here is the source object (untracked), copy everything about it into the tracked object?
Just to be clear I don't think that getting the object first and updating the properties on that should be done outside of the repository. That seems to not only force you to pass your data models across domain boundaries but seems like instead of an equivalent SQL like statement being (update x,y where id = 1), to (insert into temp table where id = 1, for reach row in temp table, update x..... now for each row in table update table x = tempx where id = 1)
Edit --
So the problem is with the setValues not being a recursive call. The routine object has 2 simple properties (id and name) and one complex (ICollection ). If the item coming in has the name changed and some steps changed, setValues picks up the name change but doesn't apply to the children. Is there some other way to do this? It seems a little creaky to me that I have to hand roll this functionality

From what i can tell you are creating your entity, populating properties and then attaching it to the DB. This is kinda the wrong way round with EF.
If you want to attach an object which is already in the DB but isnt being tracked, you can use attach but only changes made after the attach call are recorded to be committed to the DB. If you want to use attach make sure you make your changes after calling that method.
In addition EF only allows you to attach an object which is not currently in the object graph. So if you try to attach the same object twice (or one with the same key) you will be given an error such as the one you are seeing.

Related

Why is this unused line breaking Entity Framework?

I am updating some existing code of a former colleague and have a strange issue where an unused line is causing an error with Entity Framework. If I comment out the code tagged with //This Line!, everything works.
foreach (Place item in ListOfPlaces)
{
//This line!
List<Place> PlacesList = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).ToList();
long PlaceId = context.Places
.Where(x => x.PlaceNumberID == item.PlaceNumberID)
.Select(x => x.PlaceId)
.FirstOrDefault();
if (PlaceId != 0)
{
item.ID = PlaceId;
context.Places.Attach(item);
context.Entry(item).State = System.Data.Entity.EntityState.Modified;
}
}
If I include that line, I get the error shown here on the Attach(item) line:
Attaching an entity of type 'Place' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
I know how to fix this from a code point of view (remove the line!), but I can't work out why its breaking the application if somebody could kindly explain please.
I can't work out why its breaking the application
Looks to me like the line causes the download of some Place with ID N - see the ToList on the end? It will trigger the query to run and download data. EF creates objects from every row it receives because that's the default behavior(it can be disabled with eg AsNoTracking)
Later you try to create another object with the same primary key value and attach it to the context, but the context already knows about some object with ID 123 (for example) because the first line caused it to have been downloaded/tracked so you get an error when you try and associate another - if EF allowed both into its tracking memory it wouldn't know which one was the true authority of record that should be saved back to the db
Your interim query doesn't cause the problem, I believe, because it doesn't trigger the download of an entire entity, seeing as it just retrieves an ID
If you're trying to implement insert-if-not-exists style behavior, you should attempt to download an entity with ID x using some XOrDefault or Find, and if it results in null/default then create and add a new entity (you don't need to attach). In essence, ditch the first line, just do the ID check and if the returned ID is default, do a context.Places.Add(new Place{...}).
If you're looking for upsert, it's probably easiest to download the whole entity and then inspect if it was default or not; if it is, then make a new one otherwise edit the downloaded one.
If you're trying for "update without download" then skip the querying part entirely and attach an entity you declare as modified.
If you're after some hybrid upsets without download, I think you'll struggle, because you have to at least quiz the db as to whether it knows of an entity before you decide what to do.. or you run a raw MERGE
That entire loop makes no sense. You repeat the same twice. And as soon as you select one of the items, EF marks it as a tracked. And you can't update using another item, before the first one will be untracked or you can use the tracked item.
Try this code
foreach (Place item in ListOfPlaces)
{
var placesList = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).ToList();
if(placesList!=null && placesList.Count ==1)
{
var existedPlace = placesList.FirstOrDefault();
context.Entry(existedPlace).CurrentValues.SetValues(item);
}
// and maybe this
else context.Places.Add(item)
}
context.SaveChanges();
UPDATE
Thanks to #CaiusJard for a hint, in this case it is more efficient to use SingleOrDefault instead of ToList
.....
var existedPlace = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).SingleOrDefault();
if(existedPlace!=null)
{
context.Entry(existedPlace).CurrentValues.SetValues(item);
}
.....

Entity Framework Core1.1 - Bulk Insert or Update - InvalidOperationException

I'm running into a problem with inserting OR updating roughly 950 entities.
var coins = JsonConvert.DeserializeObject<List<Currency>>(json);
var sw = new Stopwatch();
sw.Start();
using (var ctx = CryptoContext.Get)
{
var existingCoins = ctx.Coins.ToList();
foreach (var coin in coins)
{
var existing = existingCoins.FirstOrDefault(c => c.CMC_Id == coin.CMC_Id);
if (existing != null)
{
ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
} else
{
ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Added;
}
}
ctx.SaveChanges();
var el = sw.ElapsedMilliseconds;
}
The code runs in the background of my netcoreapp1.1, with SQLite, and retrieves a list of currencies. This is done every 5 minutes with FluentScheduler. Because they're not entirely large objects I do all comparisons in memory, and try to add or update each one. My entity has a database-given ID of Id, and the API I'm retrieving from guarantees that CMC_Id is unique.
The initial insertion works fine. I get an error on the second "Update". I believe what's happening is that I'm tracking multiple entities as modified that each have an Id of 0
I was trying to follow this: https://msdn.microsoft.com/en-us/library/jj592676(v=vs.113).aspx
And the error I get is: "The instance of entity type 'Currency' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context."
I am unsure how to proceed with updating each row.
Issue here multiple entities with same key are asked to be tracked.
When you set EntityEntry.State to something then EF Core will start tracking the entity in the specific state. Since in your code, you are querying the database to find out existing entity, EF Core will start tracking the entity with given key therefore it throws above exception while setting the EntityEntry.State because there is already entity with same key being tracked.
More precisely you are trying to AddOrUpdate. There are multiple ways to achieve the behavior. Which one is the best depends on if you are adding one entity without relation or a complex graph.
The simplest method would be to just check existence instead of tracking the entity from database. Options for that would be to use AsNoTracking in your query so that EF does not start tracking it. Even more optimized way would be to just get count from database. If you are querying on PK property then count will be either 0 (non-existent) or 1 (existing entity). If it does not exist then you call Add otherwise Update.
var updatedBlog = new Blog { Id = 1, Title = "Updated" };
var exist = db.Blogs.Count(b => b.Id == updatedBlog.Id) != 0;
if (exist)
{
db.Update(updatedBlog);
}
else
{
db.Add(updatedBlog);
}
db.SaveChanges();
Since Add or Update methods start tracking whole graph, if your graph is in one consistent state, (all entities are new or all are being modified) then it would work just fine.
If your graph is somewhat inconsistent that state of each node in graph can be different (e.g. Updating a blog but it has new posts). Then you should use EntityEntry.State on individual entity. This makes sure that state is applied to only given entity and no other related entity in graph. Though you need to do above kind of check for each node in the graph. Another alternative is to use Attach method to attach whole graph in Unchanged state and then set state for individual node.
If you are having auto-generated Key values then probably you will have PK value set only when it is update else it would be CLR default. For single entity without relations, you can make that check yourself instead of querying database like above code and make decision. For graphs, you can use
db.ChangeTracker.TrackGraph(updatedBlog, n => n.Entry.State = n.Entry.IsKeySet ? EntityState.Modified : EntityState.Added);
This will set state of each node based on PK value being set or not.
Hope this helps :)

Update Exception with Entity-Framework?

Hi,
I am using EntityFramework for my ASP.NET MVC website but have some problems with the update.
This is how my update code looka like :
using (BissEntities context = new BissEntities())
{
if (adCategoryFilter.Id < 1)
context.AddToAdCategoryFilter(adCategoryFilter);
else
context.Refresh(System.Data.Objects.RefreshMode.ClientWins, adCategoryFilter);
if (context.SaveChanges() > 0)
return true;
}
return false;
When executing the context.Refresh i get the following exception :
The element at index 0 in the collection of objects to refresh has a null EntityKey property value or is not attached to this ObjectStateManager.
Stacktrace : at System.Data.Objects.ObjectContext.RefreshCheck(Dictionary`2 entities, Object entity, EntityKey key)
at System.Data.Objects.ObjectContext.AddRefreshKey(Object entityLike, Dictionary`2 entities, Dictionary`2 currentKeys)
at System.Data.Objects.ObjectContext.RefreshEntities(RefreshMode refreshMode, IEnumerable collection)
at System.Data.Objects.ObjectContext.Refresh(RefreshMode refreshMode, Object entity)
at Biss.Models.FilterModel.UpdateCategoryFilter(AdCategoryFilter adCategoryFilter) in C:\Users\Snowman\Documents\Visual Studio 2010\Projects\Biss\Biss\Models\FilterModel.cs:line 86
This is not the first time I get this problem. First I thought that it might have to do with the relations in the database but after these was removed from the effected table the same exception remained.
Where does the adCategoryFilter come from?
The adCategoryFilter is instansiated(new) and then filled with data from the ViewObject(from the website). It does have the required data like filter Id (to map the filter to correct row in db).
Pleas explain why Im getting this problem and how I could solve it.
BestRegards
Because your using ASP.NET MVC, your working in a stateless environment. That means, once a request has finished processing, there is no more "Entity Framework Memory", or "The Graph".
So, you need to explicitly tell EF you wish to add or update.
Here's how you do it:
using (BissEntities context = new BissEntities())
{
if (adCategoryFilter.Id < 1)
context.AdCategoryFilters.AddObject(adCategoryFilter);
else {
var stub = new AdCategoryFilters { Id = adCategoryFilter.Id };
context.AdCategoryFilters.Attach(stub);
context.AdCategoryFilters.ApplyCurrentValues(adCategoryFilter);
}
context.SaveChanges();
}
That is referred to as the stub technique.
In short, you create a new entity with the same entity key as the entity you are trying to UPDATE (in your case, the entity key is "Id").
You then "attach" this stub (so it's tracked by the EF internal graph), then override the values on this stub with your entity to UPDATE, then save changes.
I can't use UpdateModel, as i have a multi-layered architecture and use POCO's, custom viewmodels, etc - so i have created a custom "UpdateModel" method on my service/repository - which does a (more complicated) version of the above.
Also try not to use "if Id < 1, it's an add" with ASP.NET MVC - as if you forget to bind the ID on the view, it will be passed as 0, so even though you could be doing an update, your above code will try and do an add.
Instead be more explicit - have seperate action methods for Add/Update.
HTH.
Instead of refreshing, try retrieving the object and updating its properties using something like an auto-mapper (or UpdateModel in MVC controller)
The EntityKey is a separate thing to the id property, with some other stuff going on under the hood. Your newly created object is missing this stuff, which is where the problem is coming from.
The pattern goes a little like (not a C# guy so please excuse syntax):
var context = new MyEntities();
var originalObject = context.MyObjectSet.Single(x => x.Id == viewmodel.Id);
UpdateModel(originalObject);
context.SaveChanges();
The crucial difference is that the newly retrieved object has got the EntityKey all set correctly. You can validly use the id property to detect a new/existing object, but there is more to the EntityKey than just that property.

Using Attach with Linq To Sql and Stored Procs

I am trying to use the attach method to update an entity that was retrieve via a stored proc.
The stored proc is set up to return a specific instance, which is present in my dbml. The retrieval works as expected and returns a fully populated object. The reason I need to use a stored proc is that I need to update a property on that entity at the same time that it is retrieved.
After I have retrieved this entity, I am mapping it using AutoMapper to another model which is used in another tier of the app. This tier performs a few operations, and makes a change to the entity, and passes it back to the repository for updating.
The repository converts this business model back into a database model, and attempts to attach it to the datacontext in order to take advantage of the automagic updating.
No matter what combination of Attach(entity, true) Attach(entity) etc, and it gives me messages like "Row not found or changed" or "Unable to add an entity with the same primary key".
Does anyone have any experience with the Attach method and how it can be used to update entities that did not necessarily come from the data context using query syntax (ie in this case a stored proc)?
Thanks alot
First, if you are creating a copy of the object, making changes and then trying to attach the copied object to the same DataContext as the one with the original object in it, then this would probably result in the "Unable to add an entity with the same primary key" message. One way to handle this is:
1. Get object from DataContext
2. Make changes and map object (or vice versa - whatever order)
3. Update the original object with the new values made in the other tier
4. SubmitChanges on the DataContext containing the original object
or
Get the object from a DataContext and close the DataContext
Make your changes and do your mapping
Retrieve the object from the DataContext to which you want to save
Update that object with the values from your mapped object
SubmitChanges
Alternately, when you say you are using the proc because you need to update a property at the same time that you retrieve it, I'd need to see the proc, but if you are somehow committing this update after retrieving the information, then indeed the message "row not found or changed" is correct. This would be hard to do, but you could do it if you're loading the data into a temp table, doing the update, and then using a select from the temp table to populate the object. One thing you could try is setting that property, in the L2S designer, to AutoUpdate = Never and see if that makes the problem go away. If so, this is your problem.
1: is it the same data-context, and
2: is it the same entity instance (or one that looks like it)
This would only happen for the same data-context, I suspect. If it is the same entity, then it is already there; just call SumbitChanges. Otherwise, either use a second data-context or detach the original entity.
So if I retrieved the entity via a stored proc, is it being tracked by the datacontext?
The thing is.. I'm going from the data model, to a another model that is used by another component, and then back. Its not.. really the same instance, but it does have all the same properties.
IE
public Models.Tag GetEntity()
{
var dbTag = db.PROJ_GetEntity((int)EntityStatuses.Created, (int)EntityStatuses.CreatingInApi).SingleOrDefault();
return FromDb Entity(dbEntity);
}
var appModel = GetEntity(); // gets an Entity from a stored proc (NOT GetEntity_RESULT)
appModel.MakeSomeChanges();
_Repo.Persist(appModel);
public void Persist(Models.AppModel model)
{
var dbEntity = Mapper.Map(model);
db.Attach(dbEntity);
db.SubmitChanges();
}
This is somewhat pseudo code like.. but it demostrates pretty much exactly what I am doing.
Thanks
I'm upvoting weenet's answer because he's right - you can't use Attach to apply the changes.
Unlike Entity Framework, you can only attach an L2S object to a datacontext if it has never been attached before - i.e. it's a newed entity that you want to Insert into a table.
This does cause numerous problems in multi-layered environments - however I've been able to get around many of the issues by creating a generic entity synchronisation system, which uses reflection and expression trees.
After an object has been modified, I run the dynamic delegate against a new object from the DC and the modified object, so that only the differences are tracked in the DC before generating the Update statement. Does get a bit tricky with related entities, though.

Prevent Entity Framework to automatically attach entities

I'm using the Entity Framework, and it's entities are used are Database representation AND business object.
So it means that some entities that are manipulated should always stay detached from the context.
I managed to read and write data from the database but I have a small problem when updating:
I have a table "Stock" which is linked to one table "Warehouse".
The current process is this one (simplified, but the spirit remains, there are more fields):
a new object Stock is created and its fields are filled with some values (date...)
the current Warehouse (object pulled for the entire request from the database) is associated to the Stock object
the object is sent to the DAL method which work is to save it.
The DAL method checks if the Stock item already exist for the day (same date, depot and same type) in the database
If it exist, the method updates the volume from the pulled object and save the changes.
Else, the new Stock object is inserted.
The problem here is that when I create the new Stock object and associate it to the Warehouse, the object EntityState is automatically set to "Added". So when I perform a SaveChanges() and the Stock already exist, the line is updated AND a new Stock line is added...
What I would want is to keep the new Stock object Detached until I attach it myself. I don't want that it happens automatically.
The only solution I found is to Detach the new object from the context before saving if the object already exist.
I could also Detach() the Warehouse object but that's not a satisfying solution I think as in the real case there are more items to associate and I'm not sure that's a good idea to play with Attach() and Detach() on them.
In this case, until I "Add" it to the context myself, the object is only a "Transport" object and I'd like it to stay out of the context.
Any idea on how I could keep the Stock object detached ?
Code (it may be a little incorrect, I wrote it by memory) :
Stock stk = new Stock();
stk.Date = DateTime.Now;
stk.Volume = 100; //so far stk is "Detached" and that's cool.
stk.Warehouse = CurrentWarehouse; //stk become "Added" and that's less cool.
DAL.Stock.Instance.Save(stk);
In Save():
var existing = (from s in Context.CurrentContext.Stock
where s.Warehouse.WarehouseId == stk.Warehouse.WarehouseId && s.Date == stk.Date && s.Type == 2
select s).FirstOfDefault();
if(existing != null)
{
existing.Volume = stk.Volume;
Context.CurrentContext.Detach(stk); //I find it a stupid workaround !!!!!!
}
else
{
Context.CurrentContext.AddToStock(stk); //what I would want to do.
}
Context.CurrentContext.SaveChanges()
You probably just want to set the MergeOption to an appropriate value. NoTracking would keep everything in a detached state, and allow you to perform your manual work. There are probably other ways to do this, but I'm doing something similar by setting MergeOption to detached.
http://msdn.microsoft.com/en-us/library/system.data.services.client.dataservicecontext.mergeoption.aspx

Categories

Resources