Is there a way to set default child entities for a new entity without having to query all of them using ObjectQueries?
Sort of. You can set the EntityKey directly. This means you will not be able to access the value of the child entity, but sometimes you don't need to do that. There is information about doing this in this post.
Related
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.
I am using AutoMapper and trying out AutoMapper.Collection.EntityFramework, specifically the Persist<T> method.
My "source" is a fairly large object graph that has been converted (by AutoMapper) into some EntityFramework entities. The parent entity is called Log.
In my experimental test, I do the following:
var mapper = collectionConfig.CreateMapper();
var persistence = dbContext.Logs.Persist(mapper);
var testLog = logs.First(); // "logs" is the output of an AutoMapper.Map of a collection.
persistence.InsertOrUpdate<Log>(testLog);
Assert.IsTrue(dbContext.ChangeTracker.HasChanges());
What happens is an exception at the ChangeTracker.HasChanges call:
System.InvalidOperationException: The property 'Id' is part of the
object's key information and cannot be modified.
The stack trace is:
System.Data.Entity.Core.Objects.EntityEntry.DetectChangesInProperty(Int32
ordinal, Boolean detectOnlyComplexProperties, Boolean detectOnly)
at
System.Data.Entity.Core.Objects.EntityEntry.DetectChangesInProperties(Boolean
detectOnlyComplexProperties) at
System.Data.Entity.Core.Objects.ObjectStateManager.DetectChangesInScalarAndComplexProperties(IList`1
entries) at
System.Data.Entity.Core.Objects.ObjectStateManager.DetectChanges()
at System.Data.Entity.Core.Objects.ObjectContext.DetectChanges() at
System.Data.Entity.Internal.InternalContext.DetectChanges(Boolean
force) at
System.Data.Entity.Infrastructure.DbChangeTracker.HasChanges()
This is a fairly well-known and well-documented exception: it happens when you have an existing EntityFramework entity object and attempt to change the value of one of its primary key property fields.
But I'm not doing that anywhere.
I never set an Id value anywhere in my code. (The value for the Id property comes from elsewhere and is set by AutoMapper when the list of Log objects is created. Because of the nature of the data I'm reasonably sure that an entry for the testLog object does already exist in the database.)
I've been able to save whole collections of Log entries, created by AutoMapper, to the database, through EF, so I don't think my Log entities or the object graph have the problem. I think it's something AutoMapper.Collection.EntityFramework is somehow doing.
I did try the Persist method with a different, simpler entity, with many fewer child entities, and didn't have this problem. But I can't even tell from this error which object in the graph has the supposedly-changed Id value: half the objects in this object graph have a primary key called Id.
I can confirm that the actual value of testLog.Id isn't changed by the InsertOrUpdate. But my attempts to examine an Entry<Log> for testLog or even to look at dbContext.Logs.Local all cause the same exception to be thrown.
So: anyone have an idea why this is happening?
Thanks to Tyler Carlson of AutoMapper.Collection, I have an answer.
persistence.InsertOrUpdate<Log>(testLog);
The problem stemmed from testLog already being of type Log, which is the same type I'm using in EntityFramework. As Tyler said:
Automapper doesn't support mapping to itself, as it causes so many
problems.
[...]
What you should be doing is passing
the dto into the persist call, not the entity it mapped to. That
mapping will happen in the InsertOrUpdate call.
We also discovered that if your Entity and Dto objects share a common base class, and that base class contains the definition of the key fields, this will also happen.
So in my situation, where my Entity and Dto contained 95% of the same fields, derived from a common base class, I had to take the Id property that was the primary key out of the base class, and define it separately in the two classes. That ensured that, while the properties had the same names, they weren't in any sense the same property from a Reflection point of view.
If you're curious about the details, the discussion is here: AutoMapper.Collection Issue 40.
This question isn't code-centric, it's a question about idioms. I'm using Backbone/Marionette on the front and C#, NHibernate in the back.
I've got a few tables mapped and working for creates and updates. For simplicity, say I've got two tables, a parent and child, and the child has potentially many rows. The child reference to the parent is not-nullable, so I've got an Inverse relationship, and that's all working. The flow of data from Backbone to the Controller to NHibernate is pretty simple, and I'm using ISession.SaveOrUpdate() at the end. I can post mappings and so forth if necessary. I will say that the mappings Fluent NHibernate generates uses a Bag on the parent.
Here's a concrete example of the situation I'm trying to understand. Say I've got a parent entry with two rows in the child table. I manipulate the data so that one of the children is removed, but no other changes are made. Javascript sends over an object "tree" with the parent entry and the one child row that's left. The mappings are all handled fine, but the sql that's generated is a bunch of (unnecessary, but whatever) update statements. What I would like to happen instead is that NHibernate notices that there is only one child relationship in this new object, but there are two children in the actual database, and then NHibernate deletes that other child. The 'cascade-delete-orphans' option is not working, because the other child isn't actually being orphaned. It still has a reference to the parent in the fk column, and that column is non-nullable anyway, which is why I used the Inverse mapping option.
Is that possible to setup in the mappings? If not, what is a good way to tackle this situation?
Since you are sending an object from the client side, and then create the entity from that object and try to persist, NHibernate will not automatically delete the child entity since it does not know the child object is deleted (it only see you are only try to update one parent entity and a child entity), which is correct in my opinion. For example if you want to just update the parent entity field, then you have to load entire object graph to do it, otherwise NHibernate will delete all children since they are not loaded.
What you should do here is to load the parent entity, and remove missing child entity(s) deleted from it and then persist (instead of mapping the entity), code should look like following,
void Update(ParentDto parentDto){
Parent parent = _session.Get<Parent>(parentDto.Id);
//update parent fields
var childRemoved = //find removed child from parent;
parent.Children.Remove(childRemoved);
_session.SaveOrUpdate(parent);
}
I am using Entity Framework. I just need to get an arbitrary entity from the context so I can use reflection to look at its properties. I don't need any specific entity, any arbitrary one will do. My call should look like this:
Entity oldEntity = (Entity)_context.Set(newEntity.GetType()).
And some method should go after the last . but I don't see any that would work (find is my best bet but I can't use it since I don't know the ID's of any of the entities).
Edit:
I forgot to mention that the newEntity I am passing in is generated with a CodeDom class, so newEntity will be the same as the entities in the context, but it may have different properties, which is why I have to get an example "old" entity to compare its properties. To clarify this, imagine I have stored a Person entity with the properties:
First name
Last name
Then I want to add a new property, let's say Age. The way I'm doing this is by generating a new Person entity class with CodeDom with Age as an additional property. I have updated the entity class on disk, so I can create a new entity with the extra field, however my SQL table isn't updated.
You don't need an actual instance of a class to look at its properties.
You can just use typeof(...) like typeof(Entity).GetProperties()
I am using NHibernate to persist my entities in the database. MY entities have some relationships between them resulting into some mapped collections. I my case I use the Iesi.ISet interface to map these collections. I was wondering if it's possible for nhibernate to check if the properties containing those collections to be automatically set when I execute save if they are null.
This is how it should work. I have a property with a collection called "MyCollection" that is null before I save it to the database. I want NHibernate to set a collection to that property so that it's not null anymore on save. Is this possible?
This is what your constructors are for.
If you very much want to hide such initialization behind NHibernate you might be able to inject the code for this using an NHibernate interceptor or event listener.