I've been struggling with EF when trying to read records, then delete those records in the same transaction. I was initially using the EntityState.Deleted method, which would give an error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
But if I change it to like I have below, using .Remove(), then all is well.
What is the difference and best times to use .Remove() vs .Deleted?
How could I make this work using the .Deleted method? I have tried creating a new instance of the context to my repository to read and another to delete, but then got errors related to IEntityTracker can't track multiple instances... I also tried .Include on the initial read to load the dependent records into EF so it knows about and deletes them. I also tried .Detaching the read records first. All to no avail.
Here is the method in question. Note that I do have a generic repository which uses the .Deleted method which has served me well until this scenario (reading then deleting the same records.)
//Delete Allocation Need and AllocatedContainers for alloc need id
public ActionConfirmation<int> DeleteAllocRecords(int intFacilityId, AllocNeedSourceTypes needSourceType, int intNeedSourceId)
{
var context = new InventoryMgmtContext();
var repository = new AllocationNeedRepository(context);
//Delete Allocation Need and hence children in Allocated Containers
var srcType = needSourceType.ToString();
List<AllocationNeed> allocNeeds = repository.SearchFor(
x => x.FacilityId == intFacilityId
&& x.NeedSourceType == srcType
&& x.NeedSourceId == intNeedSourceId
).ToList();
//var deleteRepository = new Repository<AllocationNeed>(); <--tried separate instance of context to delete...no worky.
foreach (AllocationNeed allocNeed in allocNeeds)
{
try
{
//NO WORK: context.Entry(allocNeed).State = System.Data.EntityState.Deleted;
context.AllocationNeeds.Attach(allocNeed);
context.AllocationNeeds.Remove(allocNeed); <-- Works
context.SaveChanges();
}
catch (Exception ex)
{
return ActionConfirmation<int>.CreateFailureConfirmation(ex.Message, allocNeed.Id);
}
}
Remove will also remove the child objects, but using Deleted will not. You should really be using Remove for this very reason. If you really want to use Deleted, you'd have to make your foreign keys nullable, but then you'd end up with orphaned records (which is one of the main reasons you shouldn't be doing that in the first place).
1.) What is the difference and best times to use .Remove() vs .Deleted?
It appears that setting the entity's state to Deleted causes SaveChanges() to delete only that specific entity from the database, not taking into account other rows that may reference it via a non-null foreign-key column.
Remove() will take into account rows that are part of the relationship.
2.) How could I make this work using the .Deleted method?
If you have ON CASCADE DELETE behavior specified for the related rows, the database should handle it automatically. This is the default behavior when you let EF generate the database.
Related
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);
}
.....
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 :)
This question might look like a duplicate to other similar questions. But I suggest you to read the question till end and then decide if its a duplicate of some post or not?????
I have 6 tables in my database as follows:
I have some records already inserted in all tables.
Now, I am trying to update an Order.
At first, I just tried to Update the order as follows:
CurrentOrder.UpdateOrder(Order);
The UpdateOrder method in OrderClient looks like:
public Order UpdateOrder(Order Order)
{
IOrderRepository OrderRepository = _DataRepositoryFactory.GetDataRepository<IOrderRepository>();
Order updatedEntity = null;
if (Order.OrderId == 0)
{
updatedEntity = OrderRepository.Add(Order);
}
else
{
updatedEntity = OrderRepository.Update(Order);
}
return updatedEntity;
}
And in OrderRepository:
protected override Order UpdateEntity(RateDifferenceContext entityContext, Order entity)
{
return (from e in entityContext.OrderSet
where e.OrderId == entity.OrderId
select e).FirstOrDefault();
}
And then in DataRepositoryBase class I am using the below method:
public T Update(T entity)
{
using (U entityContext = new U())
{
T existingEntity = UpdateEntity(entityContext, entity);
SimpleMapper.PropertyMap(entity, existingEntity);
entityContext.SaveChanges();
return existingEntity;
}
}
At this point I got an error saying:
Multiplicity constraint violated. The role '…' of the relationship '…' has multiplicity 1 or 0..1
So, I thought that I need to delete the records which are specific to this order in all the related tables. So, I tried the below code:
using (var xaction = new TransactionScope())
{
foreach (OrderItemDetail orderItemDetail in OrderItemDetailClient.GetAllOrderItemDetails().Where(x => x.OrderId == NewOrder.OrderId))
{
OrderItemDetailClient.DeleteOrderItemDetail(orderItemDetail);
}
foreach (Dispatch dispatch in DispatchClient.GetAllDispatches().Where(x => x.OrderId == NewOrder.OrderId))
{
foreach (DispatchItemDetail dispatchItemDetail in DispatchItemDetailClient.GetAllDispatchItemDetails().Where(x => x.InvoiceId == dispatch.InvoiceId))
{
DispatchItemDetailClient.DeleteDispatchItemDetail(dispatchItemDetail);
}
DispatchClient.DeleteDispatch(dispatch);
}
OrderClient.UpdateOrder(NewOrder);
xaction.Complete();
}
Now, I get another error saying that :
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I get this error on below mentioned line in the last code-block:
DispatchClient.DeleteDispatch(dispatch);
You have two different problems. We don't have enough details to give you a specific fix, but since these are both very common EF "gotchas" I believe it's valuable to walk through what's happening.
The first error:
Multiplicity constraint violated. The role '…' of the relationship '…'
has multiplicity 1 or 0..1
This means your foreign key properties do not match. In EF, you often end up in situations where the same SQL relationship is represented multiple ways in your model.
For example, your Order class may have a collection of OrderItemDetails on it (which is populated using OrderItemDetail.OrderId). Your OrderItemDetail may also have an Order property on it, which is populated using the same foreign key. If both of these properties are marked as changed but the new values don't match, EF doesn't know what new value to save to the OrderItemdetail.OrderId field. In that situation it will throw this exception. The same problem can occur if OrderItemDetail has an Order property and an OrderId property.
To avoid this problem, you have to be very careful which properties you modify. Using property mappers can be dangerous because they can "accidentally" modify the wrong properties and cause lots of problems like this. We would need to see how SimpleMapper works or how that mapping is configured to really troubleshoot.
The second error:
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
This error typically means that you are not really deleting an object. You are removing the object from a relationship collection, which just sets the foreign key to null.
Continuing with the above example, if you call myOrder.OrderItemDetails.Remove(detail) then call SaveChanges, you may think it will just delete the OrderItemDetail record from the database, but that's not really what you told it to do. You removed it from the list of order item details associated with myOrder. In order to accomplish this, EF generates an UPDATE statement which sets the OrderId column to null. Without more details on your model and code from your DeleteDispatch method it's hard to know exactly where the issue is, but the exception means it's trying to set that foreign key property to null and failing because it's non-nullable.
The "fix" is to remove items directly from the Context collection instead of the related items collection. I.e. instead of myOrder.OrderItemDetails.Remove, you should call context.OrderItemDetails.Remove. This deletes the record for real.
I have an Entity called Cost, which has a required property of CostType
The Cost class has a GetNew() method which sets all the Cost's defaults:
public static GetNew()
{
Cost cost = new Cost ();
foo.CostType = Lists.CostTypes.FirstOrDefault();
// Other Default Values
return foo;
}
The Lists.CostTypes is a static list which is pulled from EF at startup and used in ComboBoxes
I am having problems setting the CostType within my code, after first setting it in the GetNew() method.
For example, the following code reads an excel file, and sets the default type based on a column in the Excel file, or null if it can't find a match
Cost cost = Cost.GetNew();
cost.CostType = Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString());
My problem is, during the Save operation I get the following error:
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
My Add Operation looks like this:
public static void AddObject(EntityObject obj, string entitySetName)
{
context.AddObject(entitySetName, obj);
context.SaveChanges();
}
If I remove the line of code that manually sets the Cost when it reads the excel file, the save works fine.
If I change the line of code to read Lists.Costs[2], it saves fine.
If I remove the line of code in GetNew() which sets the default, I get an error telling me that I violated the PK rule of CostTypes, meaning it's trying to insert the Cost Type.
Changing the ComboBox showing Type to something else still gives the same error.
After loading costs from the excel file, my regular Add/Edit forms throw the same error when I change the Type and try and save. If I don't load an excel file, they work fine.
I'm still learning Entity Framework, but so far it has been nothing but a frustration and a headache to use. Does someone know what my problem is and how I can fix it?
EDIT
Here's the info requested by Slauma. I am keeping it simple and excluding unrelated objects
Costs are in one table and CostTypes are in another table. In the database, the Costs.TypeId column is not allowed to be null, and is a Foreign Key to CostTypes. The Id field for both tables is auto-generated.
My EF model is just a generic one with the two database tables added. The only change I made to it was to rename some fields and remove the CostTypes.Costs Navigation Property.
The Excel file that gets imported maps most costs to their matching CostType.Name, however it IS possible that the string in the excel file doesn't match a CostType, so Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString()) can assign aNULLvalue to theCost.Typeproperty. That doesn't seem to be a problem though, because the form still comes up with the list of costs and their default selected items. Item's with aNULLCostType do not have an item selected in the CostTypeComboBox` and trigger a validation error that must be corrected before saving.
The code to load the CostType list is
public static List<T> GetList<T>(string sortProperty)
where T : EntityObject
{
using (var context = new TContext())
{
return ApplyOrder<T>(context.CreateObjectSet<T>(), sortProperty, "OrderBy").ToList();
}
}
The ApplyOrder code can be found here.
The GetList method is called from
public static class Lists
{
public static List<CostType> CostTypes { get; private set; }
static Lists()
{
CostTypes = DAL<CostEntities>.GetList<CostType>("Name");
}
}
I figured it out.... it was a mix of a few different things
Creating a new Cost and setting the Type was adding the cost to the shared data context. If that Cost wasn't included in the list of costs to save, or it failed it's validation error, or the user cancelled out of the Import dialog, the cost still existed in context.ObjectStateManager._addedObjects, even though I never called AddObject or AttachObject. Once I realized that I started callling DeleteObject on costs that were not going to be saved and it cleared up the 1st error I was getting.
The 2nd error I was getting (duplicate PK) was because I was looping through my new Costs and calling AddObject and SaveChanges on each one. Since setting Cost.Type to an attached CostType was automatically adding my Cost to the context, the first cost to get saved was actually adding all the new Costs to the database while the 2nd cost was trying to call AddObject/SaveChanges on what EF saw as an object that already existed
Here is not really a satisfying answer but a mix of guesses and open questions based on your infos in the question and in the comments to your question:
First of all: Your list Lists.CostTypes contains obviously entities which are detached from the context where you are later adding and saving new objects in. Because you have a using block: using (var context = new TContext()) you are retrieving your CostType entities in another context.
To tell EF that these CostType entities already exist in the database you must attach the entities to your second context (context.CostTypes.Attach(costType)) where you save your changes in (or use the same context in your method where you retrieve the list). I don't see in your code that you do this. (CostType is a navigation reference property, not a foreign key property, right?)
On the other hand when the CostType entities are not attached you should get duplicated CostTypes in your database because EF will consider them as new objects (to insert in the DB) when you call AddObject for your Cost entity since EF will always put the whole object graph of detached entities into Added state. Do you get duplicated CostTypes in the DB in your working examples? If not, something important is missing in your code snippets.
The last paragraph assumes that the key for CostType is autogenerated in the DB, as you said. If not, you would get a PK constraint violation instead of duplicated entities.
If the keys for CostType and Cost are really autogenerated identities, I am wondering where the PK violation you mentioned can come from. Every insertion would create a new unique primary key. There could never a PK violaton occur. Can you show the exception message in detail?
Did you check that all Cost entities you want to save really have a non-null CostType property (after the user has fixed all validation errors)? I cannot see any other possible reason in your code why you would get your "Relationship-could-not-be-changed-exception", except that at least for one of the Cost objects CostType is null.
Problem
I got two related tables, with a foreign key that doesn't enforce FK contraints. The "child" table should be allowed to specify a "parent" key, which is either NULL or non-existing ID.
My data access is built using Linq2Sql and it generates a one-to-many relation in the model of my tables.
The problem comes in some cleanup code, which will daily look through all the changes for the last day and fix any "errors" in the data.
Example
foreach (var user in data.Users)
{
// Check if the user has a specified office, which does not exists.
if (user.OfficeId != null && user.Office == null)
{
user.OfficeId = null; // This throws exception
}
}
Exception: System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException: Operation is not valid due to the current state of the object
This is normal behavior and somewhat expected, so I tried to set the reference object instead. But how do I define an "empty" Office?
var emptyOffice = new Office();
user.Office = emptyOffice;
The above code sets the OfficeId to NULL on the user instance, but obviously it fails during update as it tries to add a new "empty" Office to the database.
Possible solution
What I'm left with is running my own SQL which does the update, though it would be nice to actually be able to do this through Linq2Sql as there are other columns on the same row I'm updating when the related "parent" is non-existing.
Notes
There are some requirements here that is important for any comments:
The database cannot be changed.
Multiple systems is dependent on the same database schema.
Can potentially change to Entity Framework, if it supports such a scenario.
Running a custom SQL is not a problem and I'll be doing that until I find a better solution.
Would like to learn and know how to do this without any special code.
My code has FKs enforced, so I haven't tested this, but have you tried explicitly setting
user.Office = null;
That should force the FK value OfficeId to null, too.
If that doesn't work, then it the following certainly should:
user.Office = new Office(); // or keep a cached dummy Office object to save time
user.Office = null;
That will force the FK reference to realize that its value has been changed and set to null.