Efcore: Is the table structure for 1:1 relations backwards?

Created on 21 Dec 2016  路  14Comments  路  Source: dotnet/efcore

Will try to make this as concise as possible:

image

Given the example above, the relation to UserInformation is defined in my User class. My UserInformation rows are not really useful unless they specify a UserId, but I'll always be navigating from the other direction. That is to say: User can be complete without a UserInformation, but a UserInformation is always incomplete when it doesn't have a User.

Additionally, if I set the relation up on both sides, EF gets tripped up. And really, I don't need to specify the User relation going back the other way as I'm always coming from the other direction.

I'm wondering if EF landed on the wrong default policy for the columns being created here. There might be a perfectly valid reason for it (let me know!), but my User object doesn't need to store a foreign key for 1:1 relations that it has, tables that relate to my User almost always seem like a better spot to put the foreign key.

So, I get that I can customize this, but I'd hate to have to do this once for every 1:1 in my system. Is there maybe even just a flag I can flip to tell EF to put the foreign key on the _related_ side, not the _relating_ side?

closed-by-design

All 14 comments

@atrauzzi Can you show the entity types and model builder configuration that generates this?

Sure thing. I've pulled out unrelated fields just to keep the noise down.

namespace Domain
{
    public class User
    {
        public Guid Id { get; set; }

        public UserInformation Information { get; set; }
    }
}
using System;

namespace Domain
{
    public class UserInformation
    {
        public Guid Id { get; set; }
    }
}

I haven't done any model builder configuration because I figured the defaults would do the right thing. Which is more or less the basis of this issue. When setting up a 1:1 relation, the defaults are putting the FK on the wrong side.

@atrauzzi For that model it is ambiguous as to whether the relationship should be 1:1 or 1:* because there is no navigation property from UserInformation back to User. What to map for this by default is something that we discussed a lot and tried several things, but ultimately decided on 1:* by default because it is often what is intended and even when not intended will often work correctly for 1:1 relationships as well.

To be honest, I completely agree with what you just said, but still don't see how that explains why the key is on the table that doesn't depend on it.

It's got to be beyond rare where you'd need/benefit from having the FK on the side the navigation property is defined.

User.Information would be an instance of Information. User is complete on its own. Information instances will always be incomplete if they do not relate to a user. Yet the FK is forced on User where at the very least as a default, it has the lowest potential for semantic purpose.
This is beyond being the most common scenario when people are authoring their domain models. To the point that coming from other ORMs, what EF is doing here is quite unusual.

@atrauzzi Consider this model, which is a type of model that we see quite frequently:
```C#
public class Order
{
public Guid Id { get; set; }

public User AuditedBy { get; set; }

}

public class User
{
public Guid Id { get; set; }
}
```
The semantics of this model are that every order is audited by some user. A single user can audit many orders, so this is a 1:* relationship, and the FK must therefore go on Order. You could argue that in this case User should have a collection navigation property for Orders, but from what we see the domain model often does not have this.

This is not to say that your model isn't equally valid, and as I said, we did discuss this at length, but in considering all the feedback and models we have seen the pragmatic decision was that the 1:* interpretation more often works for people.

EF Team Triage: we discussed this and agreed not to make any changes. If the expected relationship configuration is different from what our conventions decide then the right solution is to configure the relationship explicitly.

That's unfortunate. I still say the majority of scenarios will prefer it the other way. So this is going to result in more configuration code for that majority.

@atrauzzi Just some context on our thought process. I am not expecting you to agree but it might help you understand why this decision.

When I look at the example you provided (User/UserInformation) it seems obvious that the model should be setup the way you expect. When I look at the example @ajcvickers provided (Order/User) it seems obvious that it should be the other way around. It is because my brain is doing something that the EF Core conventions currently cannot do: understand the semantics of the model!

When we looked at this originally we even consider other alternatives, e.g.:

  • Throw, because the model is ambiguous
  • Use some kind of heuristic to simulate intelligence, e.g. "is the singularized version of the name of one of the entities included in the name of the other entity?"

But in the end we decided to adopt a convention that is applied consistently. Conventions are not mean to make perfect judgements. They are just meant to implement behaviors that we pick to be the defaults but that we know sometimes will be wrong. That is why we allow conventions to be overridden by explicit configuration. At some point in the future EF Core will allow you to even replace conventions with your own as EF6 did.

Also a simple convention that is easy to learn is often better than a complex one. Once you learn what the convention will do by default, you can anticipate in what cases you will have to override it without testing it. Simpler conventions are also often faster.

I think I understand even more than what you've said and yes, most of the explanation jives - except that you say "what makes most sense", and yet when you look at your peers in the ORM space, they landed on the other side.

_Why? The tldr; is: Have another internal discussion and ask "Why?" - and be sure to get the honest answer for yourselves._

So:

  • Yes, my example makes sense, as does the example from @ajcvickers
  • At no point would I think you need to do any kind of fancy detection or error
  • Storing the foreign key on the other side is consistent too

To be honest, every point you raised balances equally in favour of either approach, yet the approach chosen still goes against what I'm arguing is the majority use case.

But:

  • Storing the foreign key on the owned side of the relation rather than the owning is inherently decoupled rather than guaranteeing bi-directional coupling
  • Storing the foreign key on the owned side shrinks the size of owning data sets retrieved when not joined in by reducing the number of unused columns (acknowledging that projections are just messy and a bother to maintain)
  • Storing the foreign key on the owned side reinforces a compositional approach
  • As I argued before, from an OO perspective it's more cohesive

So, I've provided a list of concrete reasons. Nothing abstract or driven by preference. And it all points pretty clearly to the wrong convention having been selected here.

I'll take a moment and offer full apologies - I'm not trying to be mean - but I'm calling into question the rationale because based only on what is here and what is common practice, there's some unaccounted for influence. Whether that's fear of change, legacy limitations, timeline constraints, or the dreaded and invariably false _it's too late to change now_ attitude.

Finally, the reason why I compare to other ORMs from other ecosystems is to avoid any echo chambers. I'd guess there will be many people acclimated to always having to write extra code for the norm rather than the exceptional case in EF. But that kind of isolation and preference can be deadly for software communities.

or the dreaded and invariably false it's too late to change now attitude.

I'm not sure whether or not I agree with you, but on a side note if EF Core has shipped with this behavior, then it literally is too late to change unless they want to break their users and rev to v2.

Okay. Well, it's that very attitude that has ensured the same mistake was made twice. At some point you have to break the cycle. No matter what kind of reaction the user base is prone to.

Again, I don't think my list of benefits is mistaken in any way, so agree or not - and I still think the EF team is very bright - the rest of the community has outshone them.

It's okay to give in. And in a lot of ways, isn't that what we all take "core" to mean in the .net ecosystem these days? EF should want to be as good as, if not better than the competition.

@atrauzzi Are you saying you think this should be interpreted as 1:1 by default? If that is the case, then everything else you say follows on from that. However, it is also valid to interpret it as 1:*, which is what EF does. At that point there is no longer any choice where to co-locate the FK--it has to be on the dependent, and that has to be the entity with the navigation property.

@ajcvickers - That doesn't change or address any of my points about the default behaviour.

I know how it currently works, I know how the two approaches differ, that's why I'm here.

Pretty sure I've been clear about this: I'm saying the way currently chosen is the less frequent and less desirable default. I do not agree at all that both approaches are effectively identical and solely down to preference.

I've already mentioned reasons why, and really - coming back with the same opinion seems more like a triage/discussion tactic than actually acknowledging the mistake or opportunity here.

(You can see my list of advantages above. The two approaches are not identical, so I hope laying that out here explicitly keeps people from talking past it any further.)

@atrauzzi I'm trying to understand if it is your opinion that this should be by-convention mapped as a 1:1 instead of a 1:*.

Was this page helpful?
0 / 5 - 0 ratings