Track changes to collections using Entity Framework change tracker - c#

For auditing/history purposes, I am using the Entity Framework change tracker to determine, before writing changes, what has changed and serialize the changes. I can get the changed entities by calling this.ChangeTracker.Entries() in my DbContext derivative and looking at the values for anything marked EntityState.Added, EntityState.Deleted, or EntityState.Modified. This all works great.
My problem is that this method does not work to track changes to collections of EF objects (for instance, an ICollection<Person> property on a PersonGroup object).
I'm sure the EF context must track this somehow -- how else would the database update work, after all? But is it available to me?

What you're looking for is relationship change tracking. You can find it in ObjectStateManager of the underlying ObjectContext, here is how you get all added relationships:
//you need to call DetectChanges
((IObjectContextAdapter)context).ObjectContext.DetectChanges();
var addedRelations = ((IObjectContextAdapter)context).ObjectContext
.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
.Where(e=>e.IsRelationship).ToList();

It turns out you can get at the relationships with this code (assuming it's running inside your DbContext derivative):
((IObjectContextAdapter) this).ObjectContext.ObjectStateManager
.GetObjectStateEntries(EntityState.Added)
.Where(e => e.IsRelationship)
.Select(r => new {EntityKeyInfo = r.CurrentValues[0],
CollectionMemberKeyInfo = r.CurrentValues[1], r.State});
Obviously you can tweak this based on what you need and it's up to do you something useful with it. The first two CurrentValues entries represent EntityKey objects which will allow you to get the IDs of the entities in question.
If you want to deal with deleted entities this won't work and you need to use reflection. Instead of CurrentValues[0] and CurrentValues[1] you can look at the internal properties Key0 and Key1, which are defined in an internal class you can't access at compile time. This will work: r.GetType().GetProperty("Key0", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(r, new object[0]). Note that this is probably not an intended use and could blow up whenever.

Related

AutoMapper confuses ChangeTracker

I have a Project object, which consists of many nested objects.
To save this object to the DB:
I change just one property within my Project (as a test), and pass it to my DAL.
I load the existing Project from the DB with EF.
I map the changed Project to the existing Project.
var existingProject =
db.Project.Single(p => p.ID == changedProject.ID).Include(p => p.Something);
existingProject = Mapper.Map(changedProject, existingProject);
db.SaveChanges();
This fails due to null foreign keys, which is no surprise because when I drill into the change tracker, I can see it's very confused:
var added = db.ChangeTracker.Entries().Where(e => e.State == EntityState.Added);
After the mapping takes place, a huge number of objects within the Project are marked as Added (even though nothing has been added or deleted from the project, I've only changed one property).
Is this because AutoMapper creates new instances of nested objects, and EF can't associate these with the existing objects from the DB?
I've seen this approach suggested in numerous places, is there a way for AutoMapper to work with the ChangeTracker so it understands the above - or is it better to just map everything manually? (a lot of work, in my case)
Destination collections are cleared first. You need AutoMapper.Collection if you want to map to existing EF collections.
This is because when You are mapping, new entity is created and EF context does not have any information about it.
You can map data from db to your 'changedProject' do all necessary changes then map again to 'existingProject' and for this entity call 'Update' method like in following example(using System.Data.EntityState.Modified)
Updating records using a Repository Pattern with Entity Framework 6

EF 4.1 DBContext and Navigation Properties

We've been using EF STEs for a while, but our application has grown quite a bit and we decided to sue the new 4.1 DbContext so we can "evolve" a separate business layer on top of our data layer without having to use different types for it.
In the elementary evaluation for the DbContext way of doing things, I am facing a little problem.
I am used to query and preload required related data like:
return context.Orders.Include("Detail").SingleOrDefault(ord => ord.ID == ID);
And then send the returned object to the UI for modification, and when returned from the UI save the changes to the database.
From what I read so far, doing the "change saving" in DbContext is easily done using code like this:
context.Entry(order).State = EntityState.Modified;
The problem with this code is that it actually marks all properties in the object as modified, a thing that's not allowed for some properties in my model (a business rule).
I resorted to the following solution (which seems to require a lot of code for a relatively small requirement! BTW, changing a modified property state to Unchanged is not supported):
context.Orders.Attach(order);
DbEntityEntry<Order> ordEntity = context.Entry(order);
string[] arr =
{
ordEntity.Property(ord => ord.ID).Name,
ordEntity.Property(ord => ord.ClientID).Name,
};
foreach (string prop in ordEntity.OriginalValues.PropertyNames)
{
if (!arr.Contains(prop))
{
ordEntity.Property(prop).IsModified = true;
}
}
context.SaveChanges();
The problem I am facing with this code is that the "Attach" statement is throwing an exception saying that there is some sort of conflict in the navigation properties in the attached object, even if no changes were made to anything at all! (saving the object exactly as it was retrieved from the database).
The error message is something like:
"Conflicting changes to the role 'Detail' of the relationship 'OrdersDatamodel.FK_Order_Detail' have been detected."
The questions are:
Is there a more "elegant" way for preventing the modification of certain object properties?
Does anybody know what's going on with the exception raised when attaching the object to the context?
Thanks.
From what I read so far, doing the "change saving" in DbContext is easily done using code like this:
context.Entry(order).State = EntityState.Modified;
You rarely need to explicitly set the state. When you modify properties, assuming they are virtual, the state will automatically change to Modified without you having to set it. Otherwise, DetectChanges will pick this up during your call to SaveChanges.

Entity Framework "detach without deletion" for filtering

I'm having a problem with Entity Framework and filtering architecture.
Let's say that I have a couple of related entities, and I want to do some changes to them, based on a filter.
So, for example I have Orders and Orderlines (to put a simple example)
I have order1, with orderline1, orderline2, orderline3 relationships in the DB
Then I receive an update request for order1 but only for orderline1 and orderline3
I get the data from the db using entity framework, which retrieves an objectgraph of the order and its lines.
Is there a way to filter these entity objects so that I can work with an objectgraph that contains order1 and orderline1 and orderline3, but NOT orderline2 without that being a problem later?
Because if i remove orderline2 from the entitycollection, i get later on concurrency errors (or deleted entities, which is something i don't want)
I hope the question is clear, I know that there could be other ways (iterating and not performing updates on orderline2, so it remains the same and no changes are made) but the way the architecture was made doesn't let me do that right now.
If I could say "don't track any more changes to orderline2, just ignore any changes that I do to this particular object and descendants, just leave it in the DB the way it is", so that I can just remove it from the collection and move forward, that'd be perfect
Thanks!
You can go multiple ways as you already described yourself as well:
Iterating through all orderlines and only modifying those that need to be modified (but that isn't an option as you stated)
The alternative you described to specifically not track changes for orderline2 is not possible in a "normal" EF situation where the ObjectStateManager is responsible for change tracking (as far as I know). In a scenario with Self Tracking Entities it's more easy because every STE has it's own unique ChangeTracker on board which can be easily switched off.
But the most easy option would be to exclude the orderlines you dont want to modify in the "select" statement or the retrieval of the entities. Something like:
private void ModifyOrderLines(int orderID, List<int> orderlineIds)
{
using(Context context = new Context)
{
List<OrderLines> orderlines =
context.OrderLines.
Where(orderLine => orderLine.OrderID == orderID && orderlineIDS.Contains(orderLine.ID))
}
}
Assuming you have set up clean foreign key relationships which were translated into Navigation Properties in EF. So what you do is to get a list of OrderLines which belong to a certain order and have an ID that's in your list of OrderLines that need to be modified.
Afterwards you change the orderlines and apply the changes to the context and call SaveChanges. This is just a basic way of how you could do things. I don't know your exact setup but I hope this helps.
EDIT
Based on your comment I should just go for the easy way and write a loop as you already proposed. Why not? I don't think there are many alternatives, and if there are then they would make things overcomplicated.
So something like this might just work:
ObjectContext.OrderLines.ForEach(o => if(orderlineIds.Contains(o.ID) {o.SomeProperty = SomeValue}));
Or you could just write the loop yourself.
EDIT2
You already mentioned detaching from the ObjectContext in the title of your post. Why don't go that way then? You tell that you have no control over the ObjectContext that you get, that it is passed into several methods and that you get update requests for certain entities. Then detaching those entities that are not needed for the update request can be an option too. Maybe this topic on MSDN might help you decide. Afterwards you might attach the detached objects again for they maybe needed for subsequent "client" calls. But this depends on how you manage the ObjectContext.
Do you keep the ObjectContext "alive" over multiple "client" calls or do you instantiate it over and over again for specific client calls. I do not get the situation totally clear...

EF code first: How to delete a row from an entity's Collection while following DDD?

So here's the scenario:
DDD states that you use a repository to get the aggregate root, then use that to add/remove to any collections it has.
Adding is simple, you simple call .Add(Item item) on the Collection you wish to add to. A new row is added to the database when you save. However, deleting is different - calling .Remove(Item item) doesn't remove the item from the database, it simply removes the foreign key. So while, yes, it is technically no longer part of the collection anymore, it's still in the database.
Reading around, the only solution is to delete it using the data context. But according to DDD the domain object shouldn't be aware of the data context so therefore deleting will have to be done outside of the domain.
What is the right way to go about this? Or Is leaving the database full of orphans acceptable (perhaps running a routine to clear them out)?
I've solved this problem in the application I'm currently working on by using domain events; a DDD concept Eric Evans said should have been in his book.
While domain objects aren't allowed to know about the object context, an IDomainEventHandler is - I've therefore got a DomainObjectDeletionHandler which deletes 'removed' objects from the object context before control returns to my application layer and the changes are saved.
For more information, I've written a blog about my implementation of domain events and how I approached hooking everything together.
Hope that helps :)
Edit
For example, if you have an Order class which has an OrderItems collection of type OrderItem:
public class Order
{
// Other stuff
public void RemoveOrderItem(int orderItemId)
{
var orderItemToRemove = OrderItems.First(oi => oi.Id == orderItemId)
OrderItems.Remove(orderItemToRemove);
DomainEvents.Raise(new OrderItemRemoved(orderItemToRemove));
}
}
When removing a child entity from a collection, EF will leave it as orphan, removing just the foreign key.
If you don't want to explicitly remove it using the DbContext, you can use what it is called "Identifying Relationship" (http://msdn.microsoft.com/en-us/library/ee373856.aspx at the bottom).
The trick is to set a composite primary key on the child including the parent's primary key.
Once you do that, when removing the entity from the parent's collection, it will be removed from the table as well.
I do not know if this is by design, but if a detail object has a composite key containing its master object's key columns, it will be automatically deleted if you remove it from the master object's collection. If you have an Order object with an OrderID key and ICollection OrderLines navigation property, give OrderLine a composite key containing OrderID and OrderLineID.
But since I do not know if I can rely on that, the solution I've used myself is to let EF handle it the way it does, and fix up 'detached' (not in EF terms) detail objects on the call to SaveChanges(), enumerating over all modified entities and changing the state to deleted as appropriate.
I solved this scenario by configuring the reference column as required and the delete behavior as Cascade
Example:
modelBuilder.Entity<AggregateRoot>()
.HasMany(x => x.Items)
.WithOne()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
In this case, EF Core (6.x) no longer set the reference column to NULL, but deleted the record just by removing the Item from the Items collection of the aggregate root.
The decisive configuration here was the delete behavior Cascade.
Why not use two repositories?
var parent = ParentRepo.Get(parentId);
parent.Children.Remove(childId); // remove it from the property Collection
ChildRepo.Delete(childId); // delete it from the database
ParentRepo.Commit(); // calls underlying context.SaveChanges()
Assuming you're sharing contexts via IOC/DI, calling commit with one repo will commit for both, otherwise just call ChildRepo.Commit as well.

Entity Framework, AutoMapper, handling entity updates

I just started using the Entity Framework 1.0 recently and believe I am beginning to feel the pains everyone is talking about. I'm trying to use best practices so I have a set of DTO that get mapped to and from my Entities via AutoMapper.
The real catch is when I'm trying to update an object. The first gotcha was that I could not find a way to create a new entity, transfer the data from my DTO, and still have the entity ObjectContext realize that it has been changed. I used the following code:
public VideoDTO UpdateVideo(VideoDTO pVideo)
{
Video video = new Video();
Mapper.Map(pVideo, video);
context.Attach(video); //Successfully attaches
context.ApplyPropertyChanges("Videos", video); // no changes made as far as entity knows b/c it was attached in it's updated state
context.SaveChanges(); //doesn't save the entity
return pVideo;
}
I then figured, perhaps I need to just grab the entity from the database first, attach to the context, call the Map method on Mapper, then call SaveChanges. Here what I did:
public VideoDTO UpdateVideo(VideoDTO pVideo)
{
Video video = context.Videos.Where(v => v.VideoId == pVideo.VideoId).FirstOrDefault();
Mapper.Map(pVideo, video); //Error here: Can't change VideoId value on Video entity
//context.Attach(video);
//context.ApplyPropertyChanges("Videos", video);
context.SaveChanges();
return pVideo;
}
Now we get to the lovely EF issue of not being allowed to change the property, VideoId, because it's used by the EntityKey property on the Video entity. Lovely. I had setup the mappings so that when I mapped from my DTO to an EF Entity, the EntityKey property would get a value. Now I need a way to make an exception to that mapping rule, but have no clue where to begin. I suppose I could create a brand new Mapping rule right in this method and set the EntityKey & VideoId properties to be ignored, but that seems pretty sloppy. Furthermore, I'm not sure a mapping created at this point would stick. If it overrode the initial setup that allowed the DTO to map a value to the EntityKey on the entity, that would backfire in a whole different way.
Anyone have a better idea?
AutoMapper
Your first problem is that as far as I know AutoMapper is not designed to go from DTO->Entity only Entity->DTO. This could have changed recently so I'm not really sure. See this link for more information about what automapper is designed to do: The case for two way mapping
PK Mapping
You say: "Mapping rule right in this method and set the EntityKey & VideoId properties to be ignored, but that seems pretty sloppy"
I don't think thats sloppy at all. You really shouldn't touch a EntityKey/PK after its been persisted and probably should codify its staticness in some way.
Entity Framework
"Now we get to the lovely EF issue of not being allowed to change the property, VideoId, because it's used by the EntityKey property on the Video entity. Lovely."
Lovely? EF is not forcing you to not update your PK. Inside the generated models there is a property change check inside the setter for your keys. The solution would be to change the generated code. Depending on your model volatility this may not be practical but it is an option.
Try mapping to an existing object:
entity = Mapper.Map<MyDTO, NyEntity>(dto, entity);
And keep the Ignore()'s in place.
http://groups.google.com/group/automapper-users/browse_thread/thread/24a90f22323a27bc?fwc=1&pli=1
I'm in the same scenario.
The only solution I got is to Ignore the PK field in the mapping from DTO -> Entity.
Such Rule can be achieved by the following line of code during the Automapper Configuration:
Mapper.CreateMap<MyDTO, MyEntity>().ForMember("EntityPK",r=>r.Ignore());
As far as I know, the only way to get EF works with Detached Entities is mapping the DTO to the Entity you got from DB before the SaveChanges (as you did in the example).
This may help if you want to avoid putting .Ignore()s on every Entity you want to Map.
http://www.prosoftnearshore.com/blog/post/2012/03/14/Using-AutoMapper-to-update-Entity-Framework-properties.aspx
In essence, you'd configure AutoMapper to ignore all Entity properties that are not scalar:
AutoMapper.Mapper.CreateMap<EntityType, EntityType>()
.ForAllMembers(o => {
o.Condition(ctx =>
{
var members = ctx.Parent.SourceType.GetMember(ctx.MemberName); // get the MemberInfo that we are mapping
if (!members.Any())
return false;
return members.First().GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).Any(); // determine if the Member has the EdmScalar attribute set
});
});
Perhaps some additional work can be added to avoid resetting if the property is a PK (a property in the EdmScalarPropertyAttribute instance (EntityKey == true?) tells you this).
Please be aware that example provided by "Mauricio Morales" will work only if you do not use prefixes. If you use them then you need to change above code slightly in more or less way like this:
Mapper.CreateMap<tempOR_Order, OR_Order>()
.ForMember(m => m.OR_ID, exp => exp.Ignore())
.ForMember(m => m.OR_CU_ID, exp => exp.Ignore())
.ForAllMembers(o => o.Condition(ctx =>
{
var members = ctx.Parent.SourceType.GetMember(ctx.MemberName); // get the MemberInfo that we are mapping
if (!members.Any())
{
members = ctx.Parent.SourceType.GetMember("temp" + ctx.MemberName);
if (!members.Any())
return false;
}
return members.First().GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).Any(); // determine if the Member has the EdmScalar attribute set
}));
That is, you need to include additional checks inside if (!members.Any()) statement. Without this, function return false and mapping will not work.

Categories