.Net4 Entity Framework, N-Tier (so objects are detatched)
I have 2 objects generated Database first such that object1 has a Navigation Property (1 - 1) to object 2.
I can successfully make changes to other properties of object 1, but when I try to change object2 I get an error.
My webpage has a drop down list of object2 names and indices.
I have tried setting the object1.object2Id property and saving it and I get a referential Key error. (I can see that this may be because the object still holds the original object2).
If however I load in the new object2 and attempt to update object1 I get the object could not be added or attached because its EntityReference has an Entity Key Property that does not match.
So I seem to be going round in circles.
So using Detached objects and Entity Framework, what is the correct way of updating a child object / foreign key?
Ok, Figured it out. Its a result of me working in detatched mode. If I wait until I am at the Business tier about to write the changes, and change the fk once I have re attached the object to the context it works.
Just one of things I needed to learn I guess !
Related
I am trying to update a foreign key using Entity Framework 7. But it is giving error: The property 'Y' could not be found in object 'X'. I have tried many different solution but still not working. The sample code:
class X
{
property Y {get; set;} -> property Y is a foreign key and also a complex type
}
In table 'X' we have a column 'Y_ID' which is the foreign key.
Note: I just want to update the foreign key. E.g. Initially class 'X' is pointing to 'NULL', I want to update class 'X' to point to 'Y1'
The Entity Framework 7 code:
var x = this.GetX();
this.mainContext.Xs.Attach(x);
var xEntry = this.mainContext.Entry(x);
xEntry.Property("Y").CurrentValue = "Y1"; // Error at this line
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
Detailed Error:
The property 'Y' on entity type 'X' could not be found. Ensure that the property exists and has been included in the model.
Edit
The approach Fabien suggested in his comment works fine. But the problem is we only know about which property to update is at runtime. If I use reflection to achieve this, the problem is entity framework treats the object as new and tries to create it (INSERT) and then throws Primary Key violation (No duplicate entries allowed)
So, is there a way where I can't still update an object property which acts like a foreign key in EF? (I don't know exact property at compile time).
If you get the entities "X" and "Y" from your context, then they're automatically tracked by the ChangeTracker. So if you assign "Y" property of the "X" object with an "Y" instance retrieved from your context and call SaveChanges or SaveChangesAsync, EntityFramework will automically do the stuff for you.
var x = this.GetX();
x.Y = "Y1";
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
By convention, your property "Y" on object "X" should be virtual to indicate that it's an foreign key.
Edit 1 :
If I understand correctly, you want to update properties of your object dynamically at runtime, with values that comme from a web api.
1st way :
Like you did, you can attach your "X" object to your context instance to begin tracking of the entity with EntityState.Unchanged, and then flag each property that need to be updated :
this.mainContext.Xs.Attach(x);
var entry = this.mainContext.entry(x);
entry.Property(p => p.Y).CurrentValue = "Y1";
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
When attaching an entity, you can specify the GraphBehavior, it tell EntityFramework if navigation properties should traversed or not.
2nd way :
Using the DbSet.Update() method :
this.mainContext.Xs.Update(x);
await this.mainContext.SaveChangesAsync().ConfigureAwait(false);
It's automatically begin tracking of the entity with the state EntityState.Modified, all properties will be marked as modified. You should watch out when using this method, because all properties will be updated, if some of them are not inititialized in your "X" object, you could lost some data. To prevent that case, you should always validate inputs.
If you want to keep your domain models de-coupled form any ORM, then you should think to separate entity types and domain types. You can use an object mapper like Automapper to map entity to domain type and vice versa. In that way you clearly separate what you do at data access layer and business logic layer.
I am modifiying the foreign key property on an entity in code, by modifiying the Id only:
ElementData.ServiceLevelId = parameter.ServiceLevelId;
I have found, after persisting, that this only works as expected, when the corresponding navigation property ServiceLevel was null by accident. If it still holds the "old" object, the change will not hit the database.
This means, I need to do
ElementData.ServiceLevelId = parameter.ServiceLevelId;
ElementData.ServiceLevel = null; //Force the update to the Database
Does that mean, that changing the object is "stronger" than changing the id only? Should I always set the related object to null in such situations?
Update (per Tim Copenhaver's comment): The entity in question is a copy (with the mentioned modification) of an existing one. It uses Automapper for copying, and maps everything except the primary key and one unrelated property. Automapper creates a shallow copy AFAIK. Thus, the situation for the copy will be that the updated Id and the untouched object reference will not match at the moment of adding it to the context. I guess, that EF then decides that the "object reference is stronger".
Changing either property will work as long as your data mapping is correct. EF is smart enough to see which of the properties has changed and ignore the other one. You have to be careful, though - if ElementData.ServiceLevel.Id does not equal ElementData.ServiceLevelId, you will get some obscure errors.
If you're having trouble with it not saving, your mapping layer is probably not correct. We can help troubleshoot if you can post the mapping for your ElementData class and some more code around how you're doing the save.
Whenever I add a foreign key entity to my previous entity by setting the ForeignKey-ID, the associated object is null.
Let me explain this:
In a previous step I've set the AddressId property to 28 and have saved the entity context by calling context.SaveChanges().
Now why is AddressId filled, but Address as the NavigationProperty (which should be an Address object of the Address table where Address.Id == 28) is null?
Entity Frameworks (EF) work this by design.
Updating the foreign key never updates the navigation property.
However, updating the navigation property will update the key. Also note that in this case the Address entity should come from the same context. If not .SaveChanges() will consider the Address entity as new and try to add it in the database.
As to the question of which method is better, well, it depends!
- Updating the Key is straightforward and is what we have been doing all along using Data Transfer Objects (DTOs) or even plain SQL. So is easier for newcomers to EF to grasp and use.
- Updating the navigation property is where you truly get an object based data model. The code looks cleaner and more readable. However you need to be very careful with the Context. In my little personal experience with EF, I find that trying to update the navigation property brings more complexity than value, especially in a multi-tier architecture where the Context is hidden behind the Data Access layer.
The most important benefit of EF, in my opinion, is in query operations using LINQ-to-Entities. I have compile-time syntax check for my queries and strong typing. I can easily create an object-based result set with multiple levels of children, data-bind ready without any additional code. I rarely write SQL anymore.
I sorta fixed this by re-creating my entities and reloading then. This seems to work and re-fetch the n:m relationship navigation properties. Weird.
I want to set a foreign key on an entity. I have the foreign entity exposed in my user control, and want to set it via WinForms data binding.
Here's the catch - the foreign entity was originally loaded from another repository/DbContext, as the user control populates itself independently using its own repository.
Unfortunately this doesn't work "out of the box", as this example demonstrates:
var repository1 = GetRepository();
var categoryFromRepository1 = repository1.GetAll<Category>().First();
var repository2 = GetRepository();
var appointmentFromRepository2 = repository2.GetNewAppointment();
appointmentFromRepository2 .Category = categoryFromRepository1;
repository2.Add(appointmentFromRepository2);
repository2.SaveChanges();
This fails on at Add() with the following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
OK, so repository2 can't auto-attach the Category because it's attached to repository1. Great, so let's detach first:
repository1.Detach(categoryFromRepository1);
Which fails on SaveChanges() due to a validation error - whoops, turns out repository2 thinks it's an Added entry and trying to insert. Great, so let's attach as well to avoid this:
repository2.Attach(categoryFromRepository1);
And this works! Problem solved. I've now set the repository2-entity property to the repository1-entity, voila.
Except that this solution sucks swamp water... We have many data-bound self-populating user controls throughout the program, and manually detaching/reattaching all the foreign entity references prior to SaveChanges() is a horrible solution. Furthermore, supposing the repository we're saving via happens to have the object attached already then we get this error when we Attach():
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
None of the solutions I can think of are that great:
1) In our generic repository class on SaveChanges(), scan all foreign references on all modified entities for out-of-DbContext entity references, dynamically change them to the in-DbContext entity reference (load from DB if necessary)
2) Don't set the navigation property at all, just set the foreign key ID field (sucks0rz yo' b0x0rz)
3) Manually do these checks before save (violates DRY & persistence-ignorance principles)
4) Abandon data-binding to these properties, manually set properties & load entities from the main repository (terrible - means extra queries to the database for data we already have)
5) Fudge user controls so that they can load their data from a given repository, if required (poor solution, violates some basic design principle... but workable)
Any other ideas, plz?
Regards,
-Brendan
Given the presence of multiple DbContext instances, it seems you have multiple bounded contexts at play. Specifically, there are multiple aggregates at play, namely Category and Appointment. Due to issues such as the one you're having, it is desirable to implement references between aggregates using only the identity value - no direct object references. If Appointment references Category by ID alone, you wouldn't have this problem. It is likely though that you need the entire Category aggregate for display purposes. This requirement can be addressed with the use of the read-model pattern.
Take a look at Effective Aggregate Design for more on this.
Here's my scenario:
I am using Silverlight, RIA and POCO objects (no Entity Framework; we're working against Oracle and SP's).
I have a Parent object that contains a collection of Child objects. I have setup the Association and Composition attributes on the Parent correctly. When I want to save changes, the entire object graph gets sent to the server correctly.
The user can add one or more Child objects to the Parent.
Now, if the user adds ONE Child object to the Parent and saves it then everything works. However, when the user tries to add TWO or more new objects to the Parent and then persist, I get the classic error:
System.ServiceModel.DomainServices.Client.DomainOperationException: Submit operation failed. An entity with the same identity already exists in this EntitySet. ---> System.InvalidOperationException: An entity with the same identity already exists in this EntitySet.
Now, this is failing on the client. I am tracing everything - the database actually gets updated! Everything gets sent down to the server correctly, the DB gets updated. I check the object keys on the server when the re-query happens and they are correct - all of the new child objects get their ID's updated from zero to a real number in sequence.
It's when I get to re-load the Parent object on the client that I get this error. I don't get it. I am newing up a new Context on the re-load operation; it should be empty and just load the Parent and associated Children. I check the data on the server side before it goes out of the query method - the parent and child data is fine. So what's happening? Why is my context bitching about not being able to complete this SubmitOperation?
You gave up too easy - don't let RIA do it for you!! :-)
Here is the deal...
Since you are working with POCO objects (no EF) you most likely have an identifying attribute ([Key]) on one of your properties designating it the key (or identity) of that object.
Well...
When you add 2 consecutive objects, you will most likely default the value of your key to a value of 0. This is a problem for the domain service & context because it attempts to manage the set for you. Well, if after the saving of the objects to the database, you have not updated the key they will both remain with a value of 0.
Problem!
The domain service & context attempt to put these two objects in its managed EntitySet and, as such, all objects must be unique.
So...
Long and the short of it is... update your key value after saving it to the database.