Vladimir
Vladimir

Reputation: 55

How to configure a foreign key on an owned EF entity's property?

EF Core 3.0

I have the following (simplified) entities in my domain:

class ApplicationUser {
    public int Id { get; set; }
    public string UserName { get; set; }
    // other properties
}

[Owned]
class Stamp {
    public string Username { get; set; }
    public ApplicationUser User { get; set; }
    DateTime DateTime { get; set; }
}

class Activity {
    public Stamp Created { get; set; }
    public Stamp Modified { get; set; }
    // other properties
}

It's not particularly relevant, but it's worth mentioning that ApplicationUser.UserName is a non-primary, unique key. (ApplicationUser actually inherits from ASP.NET IdentityUser.)

I want to enforce that Stamp.Username is a foreign key referencing ApplicationUser.UserName.

If Stamp was a regular, non-owned entity, this would have set up that relationship:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    // Stamp.Username => ApplicationUser.UserName
    modelBuilder.Entity<Stamp>(e => {
        e.HasOne(s => s.User)
        .WithOne()
        .HasForeignKey<Stamp>(s => s.Username)
        .HasPrincipalKey<ApplicationUser>(u => u.UserName);
    });
    ...
}

When I try to create a migration (dotnet ef migrations add) I get an InvalidOperationException saying "The type 'Stamp' cannot be configured as non-owned because an owned entity type with the same name already exists".

How to achieve what I'm trying to do, using Fluent API or otherwise?

Upvotes: 0

Views: 6385

Answers (3)

jeancallisti
jeancallisti

Reputation: 1681

One possible lead to explore : WithOwner() .HasForeignKey(...)

  • More modern solution (EF [core] 6)
  • Simplified solution (we use the user's Id as a foreign key instead of the user's Name, which is more standard!)

(Found in the help page https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities )

//Please note that I wasn't 100% of the syntax when using 
// 'OwnsOne<Stamp>(builder => ...)' 
// instead of 
// 'OwnsOne(user => user.Stamp, builder => ...)'

modelBuilder.Entity<ApplicationUser>().OwnsOne<Stamp>(
    subBuilder =>
    {
        subBuilder.WithOwner().HasForeignKey("ApplicationUserId");
    });

Upvotes: 2

Wouter
Wouter

Reputation: 2958

You should configure it in the owner using OwnsOne:

modelBuilder.Entity<ApplicationUser>().OwnsOne(user => user.Stamp, stamp => 
{
    // configure your stamp 
});

Upvotes: 1

Vladimir
Vladimir

Reputation: 55

This is one possible solution, likely not the most ellegant one. It is based on this answer mentioned by ChW in the comments.

modelBuilder.Entity<Activity>(e => {
    e.OwnsOne(a => a.Created)
    .HasOne<ApplicationUser>()
    .WithOne()
    .HasForeignKey<Stamp>(s => s.Username)
    .HasPrincipalKey<ApplicationUser>(u => u.UserName);
});

This sets up the required foreign key on a particular Stamp occurence (Created) for a particular owner entity (Activity).

A similar block of code would obviously need to be repeated for every other Stamp occurence (in this example, Modified) and for every other owner entity (Activity may not be the only one).

Btw, I also ended up removing the Stamp.User navigation property because EF has been using it to automatically create another foreign key that was (unwantedly) pointing to the ApplicationUser.Id property.

Upvotes: 0

Related Questions