Suppose I have a simple entity with children:
public class Entity
{
public Guid ID { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public Guid ID { get; set; }
public Guid ParentEntityID { get; set; }
}
The "Children" property is configured as a one to many relationship. There a few scenarios below that I need clarification on what will happen.
Scenario 1: Assume I load the entity but don't "Include" the Children property:
If I then initialize the collection to an empty list and save it... will that wipe out the children or do nothing? I've read that initializing collections is optional, which suggests that initializing it to an empty set is beneign. However, that contradicts the notion that the assigned set represents the related objects, in which case assigning an empty set would suggest you want to clear the related objects.
Scenario 2: Assume I load the entity and also "Include" the Children property:
If I clear the collection and save the entity, would that wipe out the children? Likewise, since it was included, if I assign a new empty set, will it remove the related objects? In other words, does assigning a new empty set and saving the entity function differently when I include the navigation property vs not include it?
Scenario 3: Assume I have thousands of Children and want to add or remove just a few of them:
Do I have to "Include" (i.e. preload) the entire collection in order to add or remove items? If so, how can I perform a partial update of the set without preloading all the children?
Scenario 1: Assume I load the entity but don't "Include" the Children
property: If I then initialize the collection to an empty list and
save it... will that wipe out the children or do nothing?
It will do nothing. No children are loaded, so none of their states are tracked. EF doesn't actually care about the collection itself, only the things inside it. You can even reassign a new collection instance, and as long as all the entities are added back into it, you're fine. You can even remove entities from the collection if you Detach them, and they won't be affected in the database. If you detach them all, you can set the collection to null or an empty collection, and it will still save changes without any error. It must perform some kind of closed-loop check on the children... seeing that their ParentID is X, it must then check Parent X to see if that child is still in it's Children collection and throw an error if it's not. It seem to analyze it all as a complete graph of the tracked entities and updates collections automatically when things are loaded. This allowed me to make a prediction that if I loaded a Parent and one of it's children separately and the Parent's Children collection is null, that should cause an error. Indeed, when I load parent by itself, the collection is initially null, and then when I load one or two children separtely, the parent's Children collection is automatically initialized/updated by EF to include the loaded children, such that clearing it would cause the foreign key error.
Scenario 2: Assume I load the entity and also "Include" the Children
property: If I clear the collection and save the entity, would that
wipe out the children?
Yes. The Include call would load all children into the tracker. It will ensure they're all in the Parent's Children collection, whether you initialized it earlier or not. The "Include" call doesn't really make it function any differnetly, it just causes all the children to enter the change tracker. Loading all the children separately after loading the parent ends up putting the entire system in the same state as if you had just called include up front.
Scenario 3: Assume I have thousands of Children and want to add or
remove just a few of them: Do I have to "Include" (i.e. preload) the
entire collection in order to add or remove items? If so, how can I
perform a partial update of the set without preloading all the
children?
Absolutely not. You can load just the ones you want to remove, then remove them from the DbSet. If you don't want to load them up front, you could issue a direct SQL delete statement on the IDs. If you don't call include and don't load any children, then you can just add a few new ones to the collection navigation property and only those few will be added; everything else that's not in the change tracker is left alone.
Related
There some cases when loading of only ID of an entity or IDs of a collection of entities (in a relationship, for example) is needed. For example I have en entity Parent and a collection of children in it
class Parent {
private List<Child> children;
}
So, when I want to load children I want only the ids to be loaded for some cases, not whole state of children. I made a research and I found a way via Named FetchGroup, which means if I want to implement this I have to add for each Entity annotation #FetchGroup(name="id", attributes = {#FetchAttribute(name = "id")}). That work for the cases when I want to apply it for only an entity of course. For children case also LoadGroup should be configured.
The question here is: is there another strategy specially for loading of ONLY ID's, when an entity or entities are requested ? Or a way to indicate that ? Here I want to avoid annotation with #FetchGroup all entities I would like to applied that, just for loading of only ID. Of course, creation of a query like "SELECT ID FROM Parent WHERE ..." or "SELECT child.id FROM Parent INNER JOIN Parent.children WHERE ..." is also not a solution here since always should be defined, for all relationships and entities.
If you just want one entity, you can use getReference() on EntityManager.
If it is a query, or relationship, then fetch groups in EclipseLink is your only option.
I have a common Repository with Add, Update, Delete.
We'll name it CustomerRepository.
I have a entity (POCO) named Customer, which is an aggregate root, with Addresses.
public class Customer
{
public Address Addresses { get; set; }
}
I am in a detached entity framework 5 scenario.
Now, let's say that after getting the customer, I choose to delete a client address.
I submit the Customer aggregate root to the repository, by the Update method.
How can I save the modifications made on the addresses ?
If the address id is 0, I can suppose that the address is new.
For the rest of the address, I can chose to attach all the addresses, and mark it as updated no matter what.
For deleted addresses I can see no workaround...
We could say this solution is incomplete and inefficient.
So how the updates of aggregate root childs should be done ?
Do I have to complete the CustomerRepository with methods like AddAddress, UpdateAddress, DeleteAddress ?
It seems like it would kind of break the pattern though...
Do I put a Persistence state on each POCO:
public enum PersistanceState
{
Unchanged,
New,
Updated,
Deleted
}
And then have only one method in my CustomerRepository, Save ?
In this case it seems that I am reinventing the Entity "Non-POCO" objects, and adding data access related attribute to a business object...
First, you should keep your repository with Add, Update, and Delete methods, although I personally prefer Add, indexer set, and Remove so that the repository looks like an in memory collection to the application code.
Secondly, the repository should be responsible for tracking persistence states. I don't even clutter up my domain objects with
object ID { get; }
like some people do. Instead, my repositories look like this:
public class ConcreteRepository : List<AggregateRootDataModel>, IAggregateRootRepository
The AggregateRootDataModel class is what I use to track the IDs of my in-memory objects as well as track any persistence information. In your case, I would put a property of
List<AddressDataModel> Addresses { get; }
on my CustomerDataModel class which would also hold the Customer domain object as well as the database ID for the customer. Then, when a customer is updated, I would have code like:
public class ConcreteRepository : List<AggregateRootDataModel>, IAggregateRootRepository
{
public Customer this[int index]
{
set
{
//Lookup the data model
AggregateRootDataModel model = (from AggregateRootDataModel dm in this
where dm.Customer == value
select dm).SingleOrDefault();
//Inside the setter for this property, run your comparison
//and mark addresses as needing to be added, updated, or deleted.
model.Customer = value;
SaveModel(model); //Run your EF code to save the model back to the database.
}
}
}
The main caveat with this approach is that your Domain Model must be a reference type and you shouldn't be overriding GetHashCode(). The main reason for this is that when you perform the lookup for the matching data model, the hash code can't be dependent upon the values of any changeable properties because it needs to remain the same even if the application code has modified the values of properties on the instance of the domain model. Using this approach, the application code becomes:
IAggregateRootRepository rep = new ConcreteRepository([arguments that load the repository from the db]);
Customer customer = rep[0]; //or however you choose to select your Customer.
customer.Addresses = newAddresses; //change the addresses
rep[0] = customer;
The easy way is using Self Tracking entities What is the purpose of self tracking entities? (I don't like it, because tracking is different responsability).
The hard way, you take the original collection and you compare :-/
Update relationships when saving changes of EF4 POCO objects
Other way may be, event tracking ?
I am using Entity Framework 4.
I am trying to associate a new entity with an existing entity. The system ends up creating a new child entity when in fact I just want to add a reference to the child object to the parent.
There is a many to many relationship between the two entities so I cannot simply set the FK property of the parent entity. I have tried parent.ChildCollection.Add(child) which simply creates a new child object in the database. This is what I am trying to avoid.
I must be doing something obviously wrong.
thanks
updated code sample
Code sample for my Self-Tracking-Entities that I have to do client side
Right now I have something like this to get all children from server then loop through to find the one i want, then add it to the object collection
List<Service.Child> childs = _client.GetChildren();
I have to loop through that collection to find the right one to add to the parent.childs collection ie.
List<Service.Child> childList = new List<Service.Child>();
foreach (Service.Child child in childList) {
if (child.ChildId == childId)
childList.Add(child);
}
contact.Childs = childList;
If an entity originally came from the database and has its own EntityKey properties populated, using Add to link it to another entity will change its EntityState to Added. Even though it is a preexisting entity, SaveChanges will create an insert command for this entity. You should consider using Attach instead:
parent.ChildCollection.Attach(child);
Using the Attach method, you can define relationships between entities that already
exist in the ObjectContext but that have not been connected automatically.
What is the right way to delete all of the collection items of an EF entity? In the code below, DocumentItems is the collection of related document items for a document. This code proceedes on Clear() but fails on SaveChanges() because related items are connected to their document via FK and FK is mandatory. So I guess they somehow remain floating up in the air without a foreign key after Clear().
Do I solve this with a foreach loop over the collection calling Remove() on each item or is there another way?
// remove existing document items to prepare for refreshing them
existing.DocumentItems.Clear();
// adds new Document Items
PrepareInvoice(existing, collection);
_repository.SaveChanges();
This is one way of deleting the items in the collection.
VB
TEntityCollection.ToList().ForEach(Sub(o) ctx.DeleteObject(o))
C#
TEntityCollection.ToList().ForEach(x => ctx.DeleteObject(x))
Then you need to call
ctx.SaveChanges()
Clear just removes the reference but doesn't delete the entity.
In your situation
existing.DocumentItems.Clear();
All DocumentItems in the EntitySet will get cleared but you will have to Remove/Delete the actual DocumentItem or the commit with fail, just the same as it would if you tried to delete it in the database.
You need to loop through detach any references, and then delete the entity you wish to remove (unless its nullable and in your situation, it is not)
Alternatively, I have seen implementations that use clear, and an AssociationChangedHandler to automatically delete the old object. Basically, if the change is a "delete/remove" it calls DeleteObject() on the orphaned object.
Trick: When setting up the relationship between Parent and Child, you'll HAVE TO create a "composite" key on the child. This way, when you tell the Parent to delete 1 or all of its children, the related records will actually be deleted from the database.
To configure composite key using Fluent API:
modelBuilder.Entity<Child>.HasKey(t => new { t.ParentId, t.ChildId });
Then, to delete the related children:
var parent = _context.Parents.SingleOrDefault(p => p.ParentId == parentId);
var childToRemove = parent.Children.First(); // Change the logic
parent.Children.Remove(childToRemove);
// or, you can delete all children
// parent.Children.Clear();
_context.SaveChanges();
Done!
Yeah, a year old, but on a minor note... since DeleteObject takes one parameter, which is the same type as the argument for the lambda expression, you can just use:
entityCollection.ToList().ForEach(ctx.DeleteObject);
I am not sure if VB supports a similar syntax, though. Anyone?
Just to answer to Nix comment to the answer,
it seems to me that the EntityCollection.Remove() method only marks for deletion the relationships and not the entities, just as the EntityCollection.Clear() method does.
I know that documentation says that also the entity will be marked for deletion but in my test I've got the behavior I described (anyone can explain me why?).
So, if you have a one to many foreign key constraint in your conceptual model, you cannot save the changes to the context in the persistence store.
The only way I found (since I don't want to CascadeDelete) is looping through the children and invoke context.DeleteObject on each of them, thus removing the entity and the associated relationship.
How do I have to set up a property so that when using SaveChanges, the many to one relationship is saved and I don't get the: INSERT statement conflicted with the FOREIGN KEY constraint... error.
Pretty simple, I have an Ad and an AdType where there are many Ads to one AdType. There is a property on Ad:
public class Ad
{
public Int32 AdTypeId { get; set; }
public virtual AdType AdType { get; set; }
}
To cover this relationship.
When I do this:
someAd.AdType = someAdType;
The property is set just fine, but the AdTypeId is not. No worries though since I would assume this would be ok to save.
context.SaveChanges();
Problem is at this point it is trying to save the 0 value in the AdTypeId column (Causing a foreign key issue) instead of using the object assigned AdType property to figure out what it should insert into the AdTypeId column.
Things I know:
At this point someAdType is
persisted/has an id.
The AdType property is set correctly.
The AdTypeId is 0.
There is a foreign key relationshipin the database.
AdTypeId is a primary key.
I have deferred/lazy loading set to true
I haven't really tried changing the AdType since it is set up to allow lazy loading.
Ok looks like because I am using the non proxied (Made that word up... Yah) "Snapshot based Change Tracking" approach, the system has no real idea that it's changed.
In this example, Customer is a pure
POCO type. Unlike with EntityObject or
IPOCO based entities, making changes
to the entity doesn’t automatically
keep the state manager in sync because
there is no automatic notification
between your pure POCO entities and
the Entity Framework. Therefore, upon
querying the state manager, it thinks
that the customer object state is
Unchanged even though we have
explicitly made a change to one of the
properties on the entity.
Got that from here
So in order to make sure it knows to check to see if there has been a change I have to use the AcceptAllChangesAfterSave option with the SaveChanges method.
context.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
And it works. Hopefully I understand it correctly...