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.
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 have quite some entities in the DB and all of them have a column decimal DBSTATE indicating if the entry is active (1) or not (0). To make a quick work of getting the instances, I created the following generic function that returns only the active columns of entities:
IEnumerable<DBType> GetActiveEntries<DBType>()
where DBType : class, IDBEntry
{
return db.Set<DBType>().Where(e => e.DBStateInDB == (decimal)DBState.Active).AsEnumerable();
}
IDBEntry is an interface that all the model classes implement by returning its DBSTATE value, e.g. this is how REGULARCUSTOMER implements it (irrelevant parts are omitted):
public decimal DBStateInDB => this.DBSTATE
As it turns out, this is not possible, because EF can only work with its own types in queries, this is why the following non-generic function works:
IEnumerable<REGULARCUSTOMER> GetActives_TEMP()
{
return db.REGULARCUSTOMERs.Where(e => e.DBSTATE == (decimal)DBState.Active).AsEnumerable();
}
So my question: is it possible to somehow avoid writing separate functions/switch-cases for all the entities or I really stuck with that option?
I'd say you have to go around the problem.
One possible way of doing it would be to add Navigational properties (by creating a new entity type) to your entities, as DbSet<EntityState> (for example) which your entity would have a ForeignKey defining the State, this would oblige you to have a supplementary table which would contain the ID's of every entity and a bool for their state, making it clean and accessible. This also means that with your newly created Navigational property you could access it with YOUR_ENTITY.EntityState.State or YOUR_ENTITY.EntityState.Id, it would also give you the capacity to modify/access a entities State regardless of their type because of the new table. Your type enherited rules will still apply and changing the table will change the entity, regardless of Type.
But again, this is only a way of doing. It should be relatively simple to implement and can be quite powerful.
Let me know what you think.
OK, this is an interesting and most importably real urgent problem for me to solve... In order for others to neatly comprehend it, I've stretched myself to make a well illustrated post.
The Object Model
So I have this simple, easy and "beautiful" model in mind. See the first picture. (You can ignore PathEntry, it's not relevant in my situation.)
The idea is that a MediaFeedItem owns:
a collection of ThumbnailFileEntries (accesible through the ThumbnailFiles property)
at most 1 raw FileEntry (MetadataFile property) and
at most 1 MediaFileEntry (MediaFile property)
We shall refer to these last three entity types as the file entities.
Now there's more: As you can see, I am inheriting both ThumbnailFileEntry and MediaFileEntry from FileEntry, and let's not debate that! (for now), it's one of those end-of-story aspects of the design and both entity types will continue to grow later on.
This already brings me some significant issues right away in regards to the polymorphic associations induced by the relationships from the file entities to MediaFeedItem.
The first thing that you shall observe is that I have eliminated the navigation property from the derived file entities (ThumbnailFileEntry and MediaFileEntry) to the primary entity MediaFeedItem.
I do this because they already inherit that property defined in the base class FileEntry. As you can see, I do not delete the roles at the end of these associations.
The Relational Model
I shall be using the so-vastly-conceptually-superior TPT strategy for generating and mapping my Object Model to the RDB world (vs TPH/TPC).
I'm using EF5-rc, the EDMX model designer to design my model, and the EF5 DbContext Generator to generate a DbContext and POCOs cuz I wanna use the DbContext API.
As you can see, I can nicely generate the database model using the EF tools:
The Problem
When loading a new MediaFeedItem and saving it, I get the following error:
System.InvalidOperationException: Multicplicity constraint violated. The role 'MetadataFile' of the relationship 'MediaFeedModel.MediaFeedItem_MetadataFile' has multiplicity 1 or 0..1.
What am I doing wrong?
Looking at your problem one thing stands out, The FK relationship between File and MediaFeedItem is required (IE a file must have a MediaFeedItem), but in the case where you are in an extended version of File you probably dont want this.
What i think you want to do is one of the following:
change the multiplicity on MediaFeedItem_FileEntry to 0..1 - 0..1 so that it isnt required at either end
create a new extended type to handle your metadataFile type and remove the direct reference between the base type and MediaFeedItem
I personally think the second is a more elegant solution to your problem as its creating an actual type for your MetadataFile
What appears to be happening is that you are trying to create an extended type but the base type isnt actually a metadata file.
I use ISession.Query<T>().ToList() to acquire a list of objects, add them to a ComboBox so the user can browse all the objects, and then I edit one of these and call ISession.Update() with that object. However this throws a NonUniqueObjectReferenceException.
Why does this happen and what approach should I use to avoid/fix this?
Problem explanation
When you load an entity, Session keeps track of the database's primary key for the entity as well as the entity's object reference (location in memory) - for the duration of the Session.
The NonUniqueObjectReferenceException is thrown if you attempt to persist an entity that has the same primary key but a different object reference as an already loaded entity for this Session.
In other words the Session is telling you, "I have an entity in memory with the same primary key as the entity you are trying to persist, but the object reference of my copy doesn't match your copy."
Problem example
Open a Session (#1).
Load the entity (object reference = A, primary key in database = 1).
Close the Session.
Open a new Session (#2).
Load the same entity again (this time, object reference = B, primary key in database = 1).
Change a property on object A and persist it inside Session #2.
NonUniqueObjectReferenceException will be thrown.
It's worth noting that this exception will be thrown, even if object A is simply part of a larger object graph that is being persisted in session #2 (even if object A didn't change).
It's also worth noting that you can load an entity directly (Session.Load, Session.Get, Session.QueryOver, etc) or indirectly (with a query that doesn't return the object, but causes the object to be loaded into memory). The NonUniqueObjectReferenceException can be thrown for both a directly or in-directly loaded entity.
Important note: This problem can be caused in other ways, for example it can be caused with a single Session if you load and then clone an entity and then use this clone to persist some changes using the Session. Reason being, the clone's object reference would differ from the original entity.
Solution explanation
There is a method called Merge on the Session object:
object Session.Merge(object obj)
Merge will take an entity and, using the entity's primary key, retrieve the already loaded version of this entity from the current Session. It will also update the properties of the Session's already loaded entity if they differ from the entity you have just passed.
This method does not change the entity that you pass in, but instead returns a different object which you should use.
One last note about Merge is that if the Session you are in doesn't have a loaded copy of this entity in memory, Merge will go ahead and load the entity from the database before performing it's usual merging functionality.
Solution example
// using the example above, we are at the beginning of step 6 inside session #2
// we have 2 important objects = ISession sessionTwo, Option objectA.
// Option is an entity defined by you, it is not part of NH.
objectA.SomeProperty = "blah";
var optionFromSessionTwo = (Option) sessionTwo.Merge(objectA);
// this will not throw and it will persist the changes to objectA
sessionTwo.Flush();
Hope this helps!
Update method as stated by Denis is used for the persistance of detached objects.This exception is thrown probably because you are trying to use Update() on those objects you probably already have in the session. You should use a transaction or Flush() in order to update your objects, if you already do so; deleting session.Update() from your code should do it.
Here is the appropriate part in the NHibernate documentation.
You need to read the documentation on:
How to use NHibernate on Winforms
What "Update" means.
The quick fix is: Don't call session.Update(), just call session.Flush() so the changes are reflected to the DB.
session.Update() doesn't update the entity, session does that transparently. Update and Save methods aren't related to INSERT and UPDATE, instead, Save() makes transient object persistent, where Update() makes detached object persisted.
I have an EF4 Model that is built with abstract entities/classes:
Notice how State entity has a navigational property called Country.
Note: I have lazy-loading disabled, so i must eager-load on demand.
Now, if i have the following method:
public Location FindSingle(int id)
{
return _repository.Find().WithId(id).SingleOrDefault();
}
This does not return any associations by default. But how can i dynamically eager-load the associations when i explicitly want to?
I cannot do this:
return _repository.Find().WithId(id).Include("Country").SingleOrDefault();
As i am working with an abstract class called Location, which does not have a navigational property called "Country". I do not know what the derived type is until i actually execute the query with .SingleOrDefault.
So, here's what i've had to do:
public Location FindSingle(int id, bool includeAssociations = false)
{
var location = _repository.Find().WithId(id).SingleOrDefault();
return includeAssociations
? LoadAssociation(location)
: location;
}
private Location LoadAssociation(Location location)
{
// test derived-type, e.g:
var state = location as State;
if (state != null)
return _repository.Find().OfType<State>().Include("Country").WithId(id).SingleOrDefault();
}
Essentially, i'm doing 2 identical calls. Does it work? Yes. Is it pretty? No, and it's not really "eager-loading".
I know this is not the correct solution, can you guys think of the proper one? (and yes i know i can use stored procedures, but i really want to go through my repository/model here, so the entities are correctly attached to the graph, ready for editing).
Even though .Include causes a Left Outer Join, the problem is i'm working off the "Locations" entity set. I need to .Include on the "State", but "States" belong to the "Locations" entity set (derived classes belong to their parent's entity set).
So i guess my question is actually pretty generic - how do we do a .Include on a child of an abstract entity, when we don't know what the child is beforehand?
Remember, i cannot use .OfType<T>() first (and then the .Include on the derived type), as i don't know what T is (and neither does the calling code), hence generics cannot be utilized here.
The real issue here is that you are holding an Id but you don't know what it represents: it could be for a Country or for a State. At some time you presumably did know what it was, but you didn't maintain that information.
After loading a Location from the repository, how do you know which type to cast it to in order to access the relevant relationship property on it? Presumably you have to use as or is with cast and then you can access these properties. Again that kinda smells bad.
The best option here would be to maintain both the Type and the Id of a Location object so you can reload it using the appropriate repository method.
Another option would be to instead move the relationship up to the Location class so that every Location object has a .Parent Location and a .Children Locations collection. Now you can include them in your Include and when you find you have a State you know to look at .Parent and when you have a Country you know to look at .Children. Use null for no parent and an empty collection for no children. Now when you add continents or cities to your Location class you'll be in great shape to use the same model.
A final option which you can sometimes use in situations like this is to Union two queries after converting them to the common base type, e.g. something like:-
context.Locations.OfType<Country>().Include("States").Cast<Location>().Union(context.Locations.OfType<State>().Include("Countries).Cast<Location>());