4

I am currently in the process of cleaning up a fairly large database. Part of the database has a relationship which is a one-to-zero-or-one mapping. Specifically:

User -> UserSettings

Not all users will have user settings, but a user setting cannot exist without the user. Unfortunately, the tables already exist. User has an PK ID. UserSettings has a PK ID and a column, User_Id_Fk which, at this point in time, is not a true FK (there is no relationship defined).

I'm in the process of fixing that and have done so from the DB perspective through SQL and have confirmed with tests. (Added the FK constraint. Added a unique constraint on User_Id_Fk.) This was all done on the UserSettings table. (Note: I am not using EF Migrations here. I have to manually write the SQL at this point in time.)

However, I now need to wire up an existing application to properly handle this new mapping. The application is using ASP.NET Core 1.0 and EF7. Here are (shortened) versions of the existing data models.

public class User
{
  public int Id { get; set; }

  public virtual UserSettings UserSettings { get; set; }
}

public class UserSettings
{
  public int Id { get; set; }

  [Column("User_Id_Fk")]
  public int UserId { get; set; }

  [ForeignKey("UserId")]
  public virtual User User { get; set; }
}

I have this Fluent Mapping as well:

builder.Entity<UserSettings>()
            .HasOne(us => us.User)
            .WithOne(u => u.User)
            .IsRequired(false);

When I go to run the application and access these items in the database, I get this error followed with a cryptic set of messages that has no information relating directly back to my application.:

ArgumentNullException: Value cannot be null.
Parameter name: navigation
Microsoft.Data.Entity.Utilities.Check.NotNull[T] (Microsoft.Data.Entity.Utilities.T value, System.String parameterName) <0x10d28a650 + 0x00081> in <filename unknown>, line 0

After doing research, someone had mentioned that the ID of the UserSettings class must be the same as the foreign key, like so:

public class UserSettings
{
  [Key, ForeignKey("User")]
  public int Id { get; set; }

  public virtual User User { get; set; }
}

I don't really have this as an option as the DB is being used for other applications I have no control over at this point. So, am I stuck here? Will I just have to maintain a 1:many mapping (which could happen now, though it hasn't) and not have proper constraints for a 1:0..1 mapping?

Update Looking at octavioccl's answer below, I tried it out without any success. However, I then removed User from the mapping in UserSettings (but I left UserId). Everything appeared to work as far as I can tell. I'm really confused what is going on here, however, and if this is even the right answer, or if I'm just getting lucky.

JasCav
  • 34,458
  • 20
  • 113
  • 170

1 Answers1

4

Remove the data annotations and try with these configurations:

builder.Entity<UserSettings>()
            .Property(b => b.UserId)
            .HasColumnName("User_Id_Fk");

builder.Entity<User>()
            .HasOne(us => us.UserSettings)
            .WithOne(u => u.User)
            .HasForeignKey<UserSettings>(b => b.UserId);

From EF Core documentation:

When configuring the foreign key you need to specify the dependent entity type - notice the generic parameter provided to HasForeignKey in the listing above. In a one-to-many relationship it is clear that the entity with the reference navigation is the dependent and the one with the collection is the principal. But this is not so in a one-to-one relationship - hence the need to explicitly define it.

The example that is presented in the quoted link (Blog-BlogImage) is pretty much the same of what are you trying to achieve.

If the solution that I show above doesn't work, then you should check if User_Id_Fk column allows null. If that is the case, change the FK property type to int?:

public class UserSettings
{
  public int Id { get; set; }

  public int? UserId { get; set; }

  public virtual User User { get; set; }
}
MarkusEgle
  • 2,795
  • 4
  • 41
  • 61
ocuenca
  • 38,548
  • 11
  • 89
  • 102
  • Thank for your response. Unfortunately, no go. I am a little bit confused with your answer where a User should only have zero or one UserSetting. I'm not understanding how your solution achieves that. (This is beyond the fact that I am still receiving the same error.) Would you be able to provide clarification? Thanks! – JasCav Apr 10 '16 at 19:17
  • I saw your update now, I think it works because EF sees now your relationship as one to many and not as one to one. When you declare just one navigation property between the related entities, EF by default creates a one to many, I'm also confuse how your current model works, because if you have the FK column in `UserSettings` table, in case that EF sees a one to many, a collection navigation property should be declared in `User`, not a reference navigation property. Indeed is very confuse, let me think again whole again, if I find something reasonable I 'll let you know – ocuenca Apr 10 '16 at 19:57
  • Yes, I agree the arrangement is kind of weird. In the end, a `User` should only have one or no `UserSettings`. Every `UserSettings` should be attached to one user. (This is enforced with a UNIQUE constraint on the FK of `UserSettings`.) I'm unsure of how to model this relationship in EF properly. I appreciate your help. – JasCav Apr 11 '16 at 00:50
  • Haha, so, I just found this answer from you (Principle/Dependent). It did the trick (I think). I don't think what I want to do is possible, otherwise. http://stackoverflow.com/questions/28499126/one-or-zero-to-one-entity-framework-code-first-fluentapi – JasCav Apr 11 '16 at 04:39