Skip to content
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

Owned attribute adds TempId column to principal entity when foreign key type has conversion #27549

Closed
Ropack opened this issue Mar 2, 2022 · 4 comments · Fixed by #29073
Closed
Labels
area-model-building closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported regression type-bug
Milestone

Comments

@Ropack
Copy link

Ropack commented Mar 2, 2022

This is breaking change between efcore 5 and efcore 6.

I have the following model where an owned entity has foreign key to another entity. The key property is of type MyId and thus needs a conversion to be configured.

If the owned entity is configured as owned via OwnedAttribute, then the referenced BarEntity entity by foreign key gets column TempId, which shouldn't be there (see the generated migration bellow).

If the owned entity is configured with modelBuilder in OnModelCreating method using .OwnsOne() (without using OwnedAttribute on OwnedBazEntity), the TempId column is not created (as expected).

Model

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

public class FooEntity
{
    public MyId Id { get; set; } = null!;
    public OwnedBazEntity OwnedBaz { get; set; } = null!;
}

public class BarEntity
{
    public MyId Id { get; set; } = null!;
}

[Owned]
public class OwnedBazEntity
{
    [ForeignKey(nameof(BarId))]
    public BarEntity? Bar { get; set; }

    public MyId? BarId { get; set; }
}

DbContext

public class MyDbContext : DbContext
{
    public DbSet<FooEntity> Foos { get; set; } = null!;
    public DbSet<BarEntity> Bars { get; set; } = null!;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(LocalDB)\MSSqlLocalDb;Database=EFCoreTest;Trusted_Connection=True");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<FooEntity>()
            .Property(x => x.Id)
            .HasConversion(x => x.Id, x => new MyId { Id = x });

        modelBuilder.Entity<BarEntity>()
            .Property(x => x.Id)
            .HasConversion(x => x.Id, x => new MyId { Id = x });

        modelBuilder.Entity<FooEntity>()
            .OwnsOne(b => b.OwnedBaz);
    }
}

Create init migration

dotnet ef migrations add Init

Actual result

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.CreateTable(
        name: "Bars",
        columns: table => new
        {
            Id = table.Column<int>(type: "int", nullable: false),

            // ========== This column shouldn't be here ========== 
            TempId = table.Column<int>(type: "int", nullable: false) 
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Bars", x => x.Id);
            table.UniqueConstraint("AK_Bars_TempId", x => x.TempId);
        });
    migrationBuilder.CreateTable(
        name: "Foos",
        columns: table => new
        {
            Id = table.Column<int>(type: "int", nullable: false),
            OwnedBaz_BarId = table.Column<int>(type: "int", nullable: true)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Foos", x => x.Id);
            table.ForeignKey(
                name: "FK_Foos_Bars_OwnedBaz_BarId",
                column: x => x.OwnedBaz_BarId,
                principalTable: "Bars",
                principalColumn: "Id");
        });
    migrationBuilder.CreateIndex(
        name: "IX_Foos_OwnedBaz_BarId",
        table: "Foos",
        column: "OwnedBaz_BarId");
}

Expected result

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.CreateTable(
        name: "Bars",
        columns: table => new
        {
            Id = table.Column<int>(type: "int", nullable: false)
            // ========== No TempId column ========== 
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Bars", x => x.Id);
        });
    migrationBuilder.CreateTable(
        name: "Foos",
        columns: table => new
        {
            Id = table.Column<int>(type: "int", nullable: false),
            OwnedBaz_BarId = table.Column<int>(type: "int", nullable: true)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Foos", x => x.Id);
            table.ForeignKey(
                name: "FK_Foos_Bars_OwnedBaz_BarId",
                column: x => x.OwnedBaz_BarId,
                principalTable: "Bars",
                principalColumn: "Id");
        });
    migrationBuilder.CreateIndex(
        name: "IX_Foos_OwnedBaz_BarId",
        table: "Foos",
        column: "OwnedBaz_BarId");
}

Provider and version information

EF Core version: 6.0.2
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Nullability: enabled
Operating system: Win10 21H1 19043.1526
IDE: Visual Studio 2022 17.1 Preview 6.0

@epicbugs
Copy link

v6.0.6 has same bug even when entity is configured with modelBuilder in OnModelCreating method using .OwnsOne()

Is there a workaround that would prevent TempId column declarations from being generated into the migration?

@whambly
Copy link

whambly commented Jul 10, 2022

Is there an update / solution to this issue? or a workaround?
I'm having the same issue with EFCore 6.0.6 having upgraded to NET 6.0
EFCore added column TempId

@ajcvickers ajcvickers removed this from the 7.0.0 milestone Jul 11, 2022
@AndriySvyryd
Copy link
Member

A possible workaround could be calling modelBuilder.Entity<BarEntity>().HasNoKey(); on the principal table before configuring the conversion and to call modelBuilder.Entity<BarEntity>().HasKey(x => x.Id); afterwards

@ajcvickers ajcvickers added this to the 7.0.0 milestone Jul 14, 2022
@AndriySvyryd AndriySvyryd added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Sep 13, 2022
@AndriySvyryd AndriySvyryd removed their assignment Sep 13, 2022
AndriySvyryd added a commit that referenced this issue Sep 13, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0, 7.0.0-rc2 Sep 14, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0-rc2, 7.0.0 Nov 5, 2022
@DawidSieczka
Copy link

DawidSieczka commented Sep 18, 2023

A possible workaround could be calling modelBuilder.Entity<BarEntity>().HasNoKey(); on the principal table before configuring the conversion and to call modelBuilder.Entity<BarEntity>().HasKey(x => x.Id); afterwards

FYI
The workaround doesn't work for modelBuilder.Entity<T>().HasKey(x => Guid1, Guid2, Guid3);
If you pass up to 2 params it works fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-model-building closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported regression type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants