-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add() behavior with graphs #2726
Comments
We should also consider whether we should mark entities without a key set as added during Attach |
Hi, maybe this article is also giving an alternative view on the topic |
@biqas indeed, I think it is related, even if Add() might not be a general purpose method for disconnected graphs. The It is possible that we won't have all the expressiveness necessary to define an aggregate in EF 7.0.0 RTM but just a convention that should figure out the expected behavior in the majority of cases. |
Updated write up to reflect some more thinking and feedback from @rowanmiller. |
Working with disconnected graph in previous version of EF was quite challenging, so for vNext I would like to see 2 different api, one for graphs, and another one for signle entities. Please try to avoid "magic" and instead give us two levels of api. Regards, Max |
From this statement I assume you definitively want one of the two levels of APIs to add single object without side effects (which is covered in our thinking). Do you have any feedback on what the second level of API should do? |
You're right! For the second level api (the graph one) It's fine for me that Add keep marking all objects as added, the unfortunate part (at lest IMHO) is with update and delete, in those cases it's quite difficult to make the right choice without knowing the aggregate boundaries, so maybe we could just avoid this api (update/delete) for graphs. Regards, Max. |
Thanks for the feedback. FWIW, "delete for graphs", e.g. cascade delete and deleted orphans behaviors after a call to |
We've implemented everything we concluded from design meeting etc. |
Should graph behavior browse through all related objects and also their relations? I tried to add an object and result was only related objects were saved not their relations. using "EntityFramework.Sqlite": "7.0.0-rc1-15945" |
With Beta8 (testing with both SQLServer & InMemory api), a child in a graph is getting added now. Not what I expected. I'm having a hard time working out if the above discussion says that you went back to this behavior. child state is added. Entry.State seems to work as expected (addressing only the root but I've only tested Add so far) |
@julielerman that is expected now, dependents get added as well. There is a new GraphBehavior enum which can be passed to the API to change this too. |
Thanks. I will look harder for discussion of this so I'm totally clear on behaviors. I looked but apparently not thoroughly enough. :) |
P.s. Cool enum! |
@julielerman I have successfully tested your scenario (Parent has an ICollection of Child, parent.Add(child)) with Beta8 (with both SQLServer & InMemory api) Unfortunatelly it does not work for all use cases, here is my scenario: I tried on EF6 and I don't reproduce this issue on EF 6, I was expecting GraphBehavior.IncludeDependents to behave like EF 6. @rowanmiller Am I missing something? Yanal |
my experience with beta 8 is that the default behavior of Add will add everything in the graph. There is a new enum parameter that allows you to specify root only.But that is not the default. I may have only tested with collection navigations but not reference navigations. Also, I have have only tested with with inmemory provider lately. I will go have a look. |
Verified that doing this with collection items passes where I add the "parent" to the context. However, my first test of doing this with a reference property fails ... but I wonder if it is because the default graph behavior on Add is includedependents and my test is starting with a dependent and setting its principal. It is not how I would really code this in my domain as the principal would be responsible for its dependents. I will go modify my model to include a one:one so I can test adding a principal and see if the related object gets included.. |
Ahh even easier was to check the design meeting notes! (https://github.com/aspnet/EntityFramework/wiki/Design-Meeting-Notes-(August-27,-2015)) Yes, the Add will only add dependents, not principals as is the case I was testing above. So now the question is: in your test, Yanal-Yves , are you using add method on the principal or dependent? I will finish my test of the reverse just to be confident. :) |
Thank you for your answer Julie! |
So if my understanding is correct I am using the add method on the principal and it should save the children whatever relation the children have with their parent (1:1, 1:many). I am wondering if there is a bug here on beta8? |
@Yanal-Yves I am not aware of any bugs in this area. Could you create an issue including a repro? |
FWIW I tested 4 scenarios:
Is this what we should expect? @divega can you tell me what I'm missing wrt how EF is inferring principal and dependent besides reading my mind (cuz it got it right) :)? |
Yes, all those cases sound like what we have built in EF7: adds propagate only from principal to dependent. And the entity that owns the FK is the dependent. |
@julielerman if you look at the overload of Add there is one that takes an enum for the two behaviors we support. The options are BTW with a one:one, we do infer from the FK, but if we get it wrong you can use the generic overload of |
Thanks Rowan and Diego. I was familiar with the enums, but hadn't thought clearly enough on the fact that you explicitly use the term dependents! Makes sense. It's not "all related objects". Big difference. :) |
Final note: |
@julielerman, I am not sure to understand, could you share some code that shows what you are explaining in your last comment? |
First thing to remember is that ef7 is not ef6 so don't expect behavior to be the same. Code looks ok but I may be missing a subtlety. Note that with ef7, one to one relationships now support FKs. Right now your childa ID property is both an FK back to parent and its own pk. It's possible that as Rowan suggested, EF is guessing wrong and doesn't see that childa is the dependent. Can you isolate the problem and take the addition of the childbs in program.cs and see what happens?
|
yep, EF needs some help configuring your model. It is assuming that childA is the "parent" and that the Parent type is the "child" aka dependent. Look at the schema of the parent table created by your model: I think EF6 would infer a 1:0..1 with this. (1:1/0..1 mappings are too hard for me off the top of my head). But it looks like EF7 isn't doing that. Which makes sense since much of the relationship magic has changed. (Am I getting this right, @divega ?) i don't know how you can do this without at least a navigation property back to the parent. You can't add a fluentapi configuration without it either. I know you are trying to make a "clean" type with ChildA. If we had complex types, I would recommend just using a complex type/value object instead of the 1:1 relationship. I tried to do a PR to just show you the new navigation & FK props in childA which are enough for convention to figure out. But that maybe I don't have correct permissions.. |
Thank you @julielerman, I have just added you as a contributor to my repo, you should be able to do a PR now. Could you try again to do a PR? |
Thank you @julielerman for your commit, with this "tweak" EF 7 is now saving the data correctly on the database. I am just wondering if adding the Parent and ParentId in the Child entity is what you guys expect from developpers to do? The real use case I am having is that I have a "User" entity and a "PostalAddress" entity. In the application I am playing with, we don't need/want to have more than one postal address per user and adding the address field in the user entity is not relevant either. The User is the Parent entity while the PostalAdress a child entity I might want to load/update depending on the use cases. |
@Yanal-Yves I'm having the exact same problem as you at the moment and I'm using this "workaround" so I can have a "clean" address object.
|
…from bottom of object graph. Apply_Changes_Should_Mark_Grandchild_Deleted now passes.
Unlike in previous versions of EF, currently calling
Add()
on an object using EF7 won't mark any of its related objects as added.We know this is not the only behavior that customers expect from
Add()
(see #2434 and #2387 as examples), however this is certainly a behavior we wanted to expose because in past versions of EF we had a lot of feedback from customers not expecting certain related objects to be marked as added.On one hand this kind of feedback has been very common among customers trying to work with disconnected graphs: Since EF does not provide a high level API for disconnected graphs, in those situations it is quite usual to use an algorithm that walks the graph and explicitly marks each individual object with a specific state, making it desirable to use an API that won't have any side effects on the state of other objects.
On the other hand even customers that used
Add()
in a standard unit of work pattern (and for whom it is very useful that the method automatically adds related objects at once) were often surprised that previous versions of EF would mark the whole graph as added.From the perspective of those customers we seem to fundamentally lack the ability to differentiate between objects which should be added from those that shouldn't, however there doesn't seem to be a single rationale that informs their expectations.
As a general approach, we have talked about having two different APIs for the two different behaviors, e.g.:
Add()
for single objects would be for customers writing lower level logic and needing the finer controlAdd()
for graphs would be for customers using the context on a unit of work pattern and wanting the API to smartly mark additional objects as addedWe could simply call them
AddSingleObject()
andAddGraph()
, but another option is to just make theAdd()
method the one that tries to automatically infer what should be added and have a separate lower level/fine control API that allows to set the state to added on a single object, e.g. what we already have in the EntityEntry.State.Regardless of the shape of the API, we need to figure out what behavior to use for
Add()
for graphs.Here are some options:
Leverage containment/aggregates:
Aggregate-oriented databases (e.g. document databases) offer a good frame of reference. Adding an aggregate root object inherently adds the whole aggregate but doesn't add anything external to the agregate.
From an O/RM perspective the key to emulate this behavior would be to have knowledge of the shape of the aggregate. As mentioned in the comments below, GraphDiff is an example of an extension for EF6 that layers this type of knowledge on top of the EF model to enable smarter decisions on state transitions, e.g. if a navigation property is "painted" with a call to
AssociatedCollection()
orAssociatedEntity()
, then adds won't spread through the relationship in the direction of the navigation property. On the other hand if the navigation property is touched by a call toOwnedCollection()
orOwnedEntity()
then adds will spread through that relationship in the direction of the navigation property.We currently don't have the ability to model aggregates on EF (it something that we have talked about adding in the future, see Take advantage of ownership to enable aggregate behaviors in model #1985), but we actually do have at least a rudimentary understanding of child vs. non-child objects, which we use (or are currently planning to use) to drive cascading/orphan deletes for
Remove()
. In that sense @tonysneed offered interesting feedback some time ago saying that he expected the behavior ofAdd()
for graphs to be more consistent withRemove()
:We could indeed use this kind of rudimentary "aggregates by convention" based on FK relationships as the basis of the behavior of
Add()
for graphs. If we later improve EF to be more aware of aggregates,Add()
for graphs would automatically pick up the new information.Leverage key values:
Another approach @rowanmiller suggested to explore is to use key values to determine which objects need to be added.
Note that basing the rules just on aggregates, the following code would insert a new Post, but would assume the associated Blog is existing:
In order to get the Blog object also added, you would need to indicate it explicitly:
Note that if the key of Blog is generated, we would actually have enough information to know whether it is an existing entity by just looking at its key. Indeed, if we leverage key values we can infer what objects need to be Added irrespective of the shape of the aggregate and the direction of relationships: assuming generated keys, the first snippet would add both Blog and Post, regardless of the fact that Blog is the principal and Post is being added.
Note that this would have equivalent semantics to the aggregate approach for a number of cases, e.g. for identifying relationships any child object of an added principal should have temporary keys too and therefore would become added automatically.
The approach per se won't work well for entities that don't have generated keys as those will look to EF as existing entities. We need to understand if that is ok or whether we would fallback to a different strategy.
Hybrid:
The idea is to figure out a way in which can leverage both aggregates and keys, e.g. leverage keys but fallback to aggregates for objects that don't have generated keys.
Modified objects on the edge
It seems that for all cases any dependent entity that we won't add but that points to a principal that is added should be updated to be able to update the FK value.
The text was updated successfully, but these errors were encountered: