EF 6 SaveChanges with Transaction - c#

I have the following issue when trying to save a brand new object tree.
I have Created an object with a number of child objects. The child objects are all 1 level deep from the initial object created. My problem is that one of the child objects that I am attempting to create also has a FK into one of the other child objects. So on the initial creation of these objects the FK's are of course 0 because the child object that hasn't yet been created has it's PK currently at 0. When I call SaveChanges on the context it errors because the key's are indeterminate on the child object that is reliant on the existence of the other child object. This is quite a large object tree and so it seems I will need to save the parent first and then other objects in order so that I can obtain the new PK value to use against those tables that are reliant on others. The trouble is I want an all or nothing situation, so I want all objects to save or none. SaveChanges in EF 6 removed the ability to pass false as a parameter and then call SaveAllChanges(). What's the best approach for me now to get this to work?

You should use transaction. In transaction, you should first add parent object to dbset and use Context.SaveChanges() the entity will not be created on db until you commit the transaction. However it will reserve id(primary key). So you can use this id on child objects. You can do the same thing for child objects, if you need id(primary key) use Context.SaveChanges().
using (var myTransaction = Context.Database.BeginTransaction())
{
try
{
//Crate Parent Object
Context.SaveChanges();
//Create Child Object;
Context.SaveChanges();
//Other Childs
Context.SaveChanges();
//If everything goes well
myTransaction.Commit();
}
catch (Exception)
{
myTransaction.Rollback();
}
}

Related

How can I attach an entity to the context when one of its children is already attached?

Background
In my application we were running into issues when trying to add a new entity with existing children to the database after mapping it from a DTO using AutoMapper. The entity and its children were not attached to the context, and so the children would be treated as new and EF would attempt to insert duplicates. My solution to this was to automatically attach an entity to the context whenever an object was mapped to a BaseEntity type (BaseEntity is the base class for all of our Model objects, it has an Id property and nothing else). Here is the code:
public TDestination Map<TDestination>(object source) where TDestination : class
{
var result = _mapper.Map<TDestination>(source);
if (typeof(TDestination).IsSubclassOf(typeof(BaseEntity)) && result != null)
_context.Attach(result); //_context is a DbContext
return result;
}
This worked fine in my initial test cases, but now I've run into an issue where the entity I'm attaching has a child that is already attached to the context. This throws "The instance of entity type 'MyChildEntity' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.".
How can I attach an entity to the context when a child is already attached? I'm trying to keep this method extremely generic so that it can be used by any object that we are trying to map from a DTO to a BaseEntity.
What I've Tried
I've tried grabbing the associated EntityEntry and recursively detach all of its children using the following method before attempting to call Attach():
private void DetachChildren(EntityEntry entity)
{
foreach (var member in entity.Members.Where(x => x.CurrentValue != null))
{
if (IsBaseEntityType(member.CurrentValue.GetType()))
{
var childEntity = _context.Entry(member.CurrentValue);
childEntity.State = EntityState.Detached;
DetachChildren(childEntity);
}
}
}
I've confirmed that this loop does reach the problem child and sets its state to detached, but I still end up getting the same error when calling Attach().
Welcome to the hell that is working with detached entities.
Automapper can be leveraged to update existing entities rather than forming an entity and attaching it. Given an object like an Order:
public void UpdateOrder(OrderDTO orderDTO)
{
var order = _context.Orders.Single(x => x.OrderId = orderDTO.OrderId);
_mapper.Map(orderDTO, order);
_context.SaveChanges();
}
The benefits of this approach is that it handles whether the order happens to be tracked or not, asserts the order exists for something like an Update where it is assumed it does, and when SaveChanges runs, only the fields that actually changed will be updated. If only 1 field changed, the update statement updates that single field. Attaching a new object and setting EntityState to Modified will update all fields. This could introduce unexpected attack vectors to change data you don't expect since a DTO needs to pass enough info to construct a whole entity to avoid unintentionally #null-ing data. The mapping from DTO to entity should ensure that only editable fields are copied across.
In the case where the OrderDTO will contain one or more child collections to update, you will likely need to use a mapping that excludes those collections, then use AfterMap in the mapping configuration to inspect the child collection for new vs. existing vs. removed entities and handle those accordingly. (Add vs. mapper.Map vs. Remove)
Generally the updates can be structured to perform atomic operations that make the entity interactions as straight forward as possible. For instance UpdateOrderDetails(orderDTO) would update information about the order itself, where there would be separate methods such as AddOrderLine(newOrderLineDTO) vs. UpdateOrderLine(orderLineDTO) vs. RemoveOrderLine(orderLineId) etc. rather than having all order line operations and other related changes done through a single UpdateOrder method accepting a whole modified object graph.
The alternative when dealing with graphs and the possibility of tracked entities is that you need to check each and every related entity against the DbSet's .Local or other means to check to see if the entity is tracked. If it is, then you have to replace the references and copy any applicable changes to the already tracked entity. Telling a DbContext to ignore an existing entity isn't always a simple matter as there can be references to that entity in other tracked entities. Generally you'll want to detect a tracked entity reference then update your references to use that tracked reference and update it as needed. It is lots of mucking around with references, and definitely does not work well with Generic methods
Generic operations are tempting from a DNRY standpoint, but dealing with situations where some entities might be tracked vs. not, and then handling type mismatches etc. (source = object = can be anything..) adds a lot of complexity in place of simpler methods to handle operations.

EF 6 Saving multiple levels of child entities and multiple parents

Given this model:
I would like to be able to save in one SaveChange call the relations. Which means, I either have a new or updated ContainerParent, and multiple first level children and each of those can have 1 or 2 levels deeper.
The thing is, the children both have a key to themselves, for finding its parent, and a key to the container, for the container to get all its Children independently of their hierarchical level.
With this pseudo code (in the case of all entities are created, not updated)
var newContainerParent = context.ContainerParents.Add(new ContainerParent());
var rootChild = context.Children.Add(new Child());
var secondLevelChild = new Child();
var thirdLevelChild = new Child();
secondLevelChild.Children.Add(thirdLevelChild);
rootChild.Children.Add(secondLevelChild);
newContainerParent.Children.Add(rootChild);
context.SaveChanges();
Problem with this code, is that only the rootchild will have the FK for the container set. I also tried to add the children to they child parent AND the container:
rootChild.Children.Add(secondLevelChild);
newContainerParent.Children.Add(rootChild);
newContainerParent.Children.Add(secondLevelChild);
newContainerParent.Children.Add(thirdLevelChild);
I have the same problem while updating an existing container with new children. I set all the children with the already existing key of the parent, but when SaveChanges is called the key is not saved, its reverted to null.
I fixed it by doing all this in 2 steps, saving once and then getting all the newly created children and updating them with the parent key, the calling SaveChanges again.
I have a feeling I'm missing something, that I should not need to save twice.
The number or frequence of SaveChange calls have no implication on anything, not on performance or so. So why do you want to minimize it ?
Actually, storing such a self referencing table with one SaveChanges is not possible,
cause the ID of an new entity, is generated, when it is saved. So you first need to save it, and then you get the ID, that you can store in another entity. This might require further update-Commands, to the entity you just stored.
You have two chances to solve this.
1) manually generated ID's, handle it all yourself and you know the ID before your store it.
2) In case you have no circularity in your dependency, so a perfect tree structure, you save the items top-down, level by level. I assume you have the childs having a reference to it's parents, so the root has no reference to any other items, you save that first, than the 1st level children, and so on.
This requires multiple SaveChanges, but this is not a disadvantage. It is one Insert-SQL-Command per entity anyway, no matter if you do it in 1 SaveChanges or in 100 SaveChanges.
Both solutions avoid "Update" Commands to the entities, they do Inserts only.
Entity Framework could actually find out this dependencies itself and create an order for new entities to insert, but this is not implemented today, or not perfect, especially on self-referenced tables. The order of saving items is kind of random. So you have to enforce the order with intermediate SaveChanges.

Nhibernate 3.3.0.4000 incorrectly orphaning child entities

I have an intermittent problem in a production system that I haven't been able to recreate locally. What appears to be happening is that for no obvious reason during an edit of a child entity and saving the parent containing the collection the parent association on the child entity is removed in the database. Effectively orphaning the child entity.
Here is the fluent mapping:
mapping.HasMany<ExpenseItem>(x => x.ExpenseItems).Cascade.AllDeleteOrphan();
The entity can also be directly saved (it is also aggregate root), but during testing this has not be shown to remove the parent association.
There are no references to the parent itself in the child object the association is all in the DB handled by Nhibernate.
There is also no possibility of assigning the same child entity to another parent as expense item (child entity) cannot be added outside of its parent directly. See code below.
public virtual ExpenseItem AddNewExpenseItem(ExpenseAnalysis analysis,
string recipientName,
string purchaseAccountReference,
string expenseDescription,
string expenseNotes,
Money value,
VATAnalysis vat)
{
Validate.IsNotNull<ExpenseAnalysis>(analysis);
Validate.IsNotNull(expenseDescription);
....
ExpenseItem newExpenseItem = new ExpenseItem(analysis,
recipientName,
purchaseAccountReference,
expenseDescription,
expenseNotes,
value,
vat,
expenseItemUniqueReference,
true,
Candidate.Assessment);
_expenseItems.Add(newExpenseItem);
....
Any ideas?
It would definitely help to see your mapping but take a look at
I'd also map the ExpenseItem with a reference to its parent
How to delete child object in NHibernate?
basically you ve to set inverse on the relationship so that NHibernate does not try to update the deleted record with a null victim. And you set Cascade.All | Cascade.DeleteOrphans so that we aren't just breaking the relationship by nulling out the victim, but deleting the entire child record.
This problem was caused by a bug in NHibernate. I had set the session to flush never however when you call isdirty on the session the session was being flushed first. The user was deleting the expense item then cancelling the edit but during the isdirty check the deletion was being flushed. Managed to work around this by changing how the session was being managed for this type of dialog.

What's the proper way to update parent/child entities in EF?

I'm using Ef 4 code first and have a parent table with a child
When I update the parent, I notice that the child is not updated, rather new entries are created (old ones are not deleted).
My update method looks like so:
IEFRepository<Parent> repo = new EFRepository<Parent>( context );
var parent = repo.GetSingle(m => m.parentId.Equals(updatedParent.parentId));
parent.Child = updatedParent.Child; //this is creating a new record in the db, not overwriting the existing
repo.Update(parent);
If I break out the child properties in the update method like below it solves the duplicate entry problem, but creates other issues elsewhere (primarily with validating null entries).
parent.Child.property = updatedParent.Child.property;
I also tried creating an UpdateChild(), and calling that from UpdateParent(), but got essentially the same result as breaking out the individual child properties
Is there a proper way to control this behavior and force EF to overwrite the child entity instead of creating a new one?
Have a list of childs in a context and remove old child from your context when you don't need it anymore.

General best practice for updating a record and its associated relationships in Linq-to-SQL

I have a very general question about updating a record in Linq-to-SQL. Suppose, in my data model, I have a base record (Table - Person) with an m:m relationship to a set of Categories (Table - Category). Therefore, I have an associative table (PersonCategory) that has foreign keys to both a PersonID and CategoryID.
When I want to update a Person, I may have new PersonCategory records to add and may want to remove other PersonCategory records. What is the best practice for doing such a thing? Would I want to remove all records in Person.RelatedPersonCategories and then add new in? Is LINQ-to-SQL smart enough to negotiate what records in the PersonCategory table are actually being added, edited, or deleted (based on looking at its foreign key potentially)?
Thanks for any help!
As long as there is an active instance of the DataContext class to track changes, LINQ to SQL will happily insert/update/delete rows in an associated table everytime objects in the collection that maps the relationship in the model are modified, and the DataContext.SubmitChanges() method is called.
For example:
using (var db = new DataContext())
{
var person = db.Persons.Where(p => p.Name == "Foo").SingleOrDefault();
if (person != null)
{
// Inserts a new row in the 'PersonCategory' table
// associated to the current 'Person'
// and to the 'Category' with name 'Employee'
person.PersonCategories.Add(new PersonCategory() { CategoryName = "Employee" });
// Updates the 'CategoryName' column in the first row
// of the 'PersonCategory' table associated to the current 'Person'
person.PersonCategories(0).CategoryName = "Consultant";
db.SubmitChanges();
}
}
Things are a little different if you are making changes to the model objects in "disconnected" mode, that is when the DataContext instance that was used to initially create those objects no longer is around.
In this case insert/delete operations on associated tables will work just fine when the object having the modified collection is attached to a new DataContext with the Table(TEntity).Attach method, followed by DataContext.SubmitChanges().
However, modifications on any of the existing objects in the collection will not automatically be applied in the associated table. In order to do that, you must manually call the Table(TEntity).Attach method for each object in the collection.
Here is a quote from the MSDN documentation:
When a new entity is attached,
deferred loaders for any child
collections (for example, EntitySet
collections of entities from
associated tables) are initialized.
When SubmitChanges is called, members
of the child collections are put into
an Unmodified state. To update members
of a child collection, you must
explicitly call Attach and specify
that entity.
Here is a concrete example:
// The 'Person' object has been detached
// from the originating 'DataContext', which is now disposed
person.PersonCategories.Add(new PersonCategory() { CategoryName = "Employee" });
person.PersonCategories(0).CategoryName = "Consultant";
using (var db = new DataContext())
{
// Will detect added and deleted objects
// in the 'PersonCategory' collection
db.Person.Attach(person);
// Required to detect and modifications
// to existing objects in the 'PersonCategory' collection
foreach (var personCategory in person.PersonCategories)
{
db.PersonCategory.Attach(personCategory);
}
db.SubmitChanges();
}
From my experience you don't have to do anything special here. PersonCategory is just an entity, and you add or remove instances from the related table instance on a data context as you would for any other entity, via InsertOnSubmit() and DeleteOnSubmit(). Updates to existing PersonCategory objects are handled by the change tracking system.
You certainly don't need to remove all items first and then add new ones back in.
It gets more complex when you look at the model code - provided you have foreign keys set up correctly in the database, the generated model code should completely handle association management as well.
Eg,
Person will have an EntitySet<PersonCategory>
PersonCategory will have an EntityRef<Person> and EntityRef<Category>
Category will have an EntitySet<PersonCategory>.
Whenever changes are made to these fields, the associated objects are updated as well:
If I change the Person property on a PersonCategory to null, the code will also update the related EntitySet<PersonCategory> accordingly by removing that PersonCategory from the prior person's EntitySet<PersonCategory>
If I add a new PersonCategory to a person's EntitySet<PersonCategory>, the code will automatically set the Person property to the new parent Person as well.
So yes, the basic answer is LINQ to SQL is smart enough to handle this for you - as long as you are generating the model code from your database.

Categories

Resources