dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.73k stars 3.18k forks source link

Only the 1st Owned Entity property can be configured by Fluent API #14096

Closed ganaware closed 2 years ago

ganaware commented 5 years ago

Only the 1st Owned Entity property can be configured by Fluent API

Steps to reproduce

A complete project is here: https://github.com/ganaware/TestEFCoreOwned

I defined an entity Book that has three Owned Entity properties: EnglishInfo, JapaneseInfo and ChineseInfo. The type of these properties is Info configured by fluend API.

    public class Book {
        public int BookId { get; set; }
        public Info EnglishInfo { get; set; }
        public Info JapaneseInfo { get; set; }
        public Info ChineseInfo { get; set; }

        public static void OnModelCreating(ModelBuilder modelBuilder) {
            var e_tb = modelBuilder.Entity<Book>();
            e_tb.Property(e => e.BookId);
            e_tb.OwnsOne(e => e.EnglishInfo);
            e_tb.OwnsOne(e => e.JapaneseInfo);
            e_tb.OwnsOne(e => e.ChineseInfo);
        }
    }

    public class Info {
        public string Title { get; set; } // a general column
        public string Author { get; set; } // a column specified HasMaxLength() by fluent API
        string Memo { get; set; } // with fluent API, a column can be implemented by a private property

        public static void OnModelCreating(ModelBuilder modelBuilder) {
            EntityTypeBuilder<Info> e_tb = modelBuilder.Entity<Info>();
            e_tb.Property(e => e.Title);
            e_tb.Property(e => e.Author)
                .HasMaxLength(100);
            e_tb.Property(e => e.Memo);
        }
    }

    public class MyContext : DbContext {
        public DbSet<Book> Books { get; set; }

        public MyContext() {
        }

        public MyContext(DbContextOptions<MyContext> options)
            : base(options) {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
            if (!optionsBuilder.IsConfigured) {
                optionsBuilder.UseSqlite(new SqliteConnection("DataSource=:memory:"));
            }
        }

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

    public class MyContextForMigrationFactory : IDesignTimeDbContextFactory<MyContext> {
        public MyContext CreateDbContext(string[] args) {
            return new MyContext();
        }
    }

Then, I created a migration by dotnet command:

dotnet ef migrations add InitialCreate

The generated 20181206004616_InitialCreate.cs is:

    public partial class InitialCreate : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Books",
                columns: table => new
                {
                    BookId = table.Column<int>(nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    EnglishInfo_Title = table.Column<string>(nullable: true),
                    EnglishInfo_Author = table.Column<string>(maxLength: 100, nullable: true),
                    EnglishInfo_Memo = table.Column<string>(nullable: true),
                    JapaneseInfo_Title = table.Column<string>(nullable: true),
                    JapaneseInfo_Author = table.Column<string>(nullable: true),
                    ChineseInfo_Title = table.Column<string>(nullable: true),
                    ChineseInfo_Author = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Books", x => x.BookId);
                });
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Books");
        }
    }

The problem is:

In short, Only the 1st Owned Entity property (EnglishInfo) can be configured by Fluent API. Configurations for 2nd (JapaneseInfo) and 3rd (ChineseInfo) are ignored.

Further technical details

smitpatel commented 5 years ago

Duplicate of #9148

ganaware commented 5 years ago

Thank you. I understand that it's my bug.

I fixed as follows:

    public class Book {
        ...
        public static void OnModelCreating(ModelBuilder modelBuilder) {
            var e_tb = modelBuilder.Entity<Book>();
            e_tb.Property(e => e.BookId);
            e_tb.OwnsOne(e => e.EnglishInfo, cb => Info.OnModelCreating(cb));
            e_tb.OwnsOne(e => e.JapaneseInfo, cb => Info.OnModelCreating(cb));
            e_tb.OwnsOne(e => e.ChineseInfo, cb => Info.OnModelCreating(cb));
        }
    }

    public class Info {
        ...
        public static void OnModelCreating<T>(ReferenceOwnershipBuilder<T, Info> rob) where T : class {
            rob.Property(e => e.Title);
            rob.Property(e => e.Author)
                .HasMaxLength(100);
            rob.Property(e => e.Memo);
        }
    }

    public class MyContext : DbContext {
        ...
        protected override void OnModelCreating(ModelBuilder modelBuilder) {
            base.OnModelCreating(modelBuilder);
            //Info.OnModelCreating(modelBuilder);
            Book.OnModelCreating(modelBuilder);
        }
    }

Everything works as expected.

Fixed project is here: https://github.com/ganaware/TestEFCoreOwned/tree/ProblemSolved