vkhorikov / DddAndEFCore

Source code for the DDD and EF Core Pluralsight course
https://enterprisecraftsmanship.com/ps-ef-core
MIT License
249 stars 90 forks source link

Issue updating the foreign key for Many to One relation ship #11

Closed JinishBhardwaj closed 3 years ago

JinishBhardwaj commented 3 years ago

Hey @vkhorikov , great course. I am hitting a snag while updating the foreign key. I have posted the question on Stack overflow as well here: Many to one foreign key null

Relation: Many products can belong to the same category. Have simplified the domain models to focus on the issue
EfCore: 5.0

Product Model

public class Product: Entity, IAggregateRoot
{
    protected Product() { }

    public Product(string name, Category category): this()
    {
        Name = name;
        Category = category;
    }

    public string Name { get; }
    public virtual Category Category { get; }
}     

Category model

public class Category: Entity
{
    protected Category() { }

    public Category(string name): this()
    {
        Name = name;
    }

    public string Name { get; }
}

Product Configuration

public class ProductConfiguration: IEntityTypeConfiguration<Product>
{
    public void Configure(EntityTypeBuilder<Product> builder)
    {
        builder.ToTable("Product");

        builder.HasKey(x => x.Id);

        builder.Property(x => x.Id)
               .HasValueGenerator<StringValueGenerator>()
               .ValueGeneratedOnAdd();

        builder.Property(x => x.Name).IsRequired().HasMaxLength(500);

        builder.HasOne<Category>().WithMany();
    }
}

Category configuration

public class CategoryConfiguration: IEntityTypeConfiguration<Category>
{
    public void Configure(EntityTypeBuilder<Category> builder)
    {
        builder.ToTable("Category");

        builder.HasKey(x => x.Id);

        builder.Property(x => x.Id)
               .HasValueGenerator<StringValueGenerator>()
               .ValueGeneratedOnAdd();

        builder.Property(x => x.Name)
               .IsRequired()
               .HasMaxLength(255);
    }
}

So having this configuration and domain models in place, when trying to insert some seed data the "CategoryId" property (shadow property created by EFCore, and not in the domain model) is always null

public class ProductDbContextSeeder
{
    public async Task SeedAsync(ProductDbContext context)
    {
        var category = await context.Categories.FirstOrDefaultAsync(x => x.Name == "Test Category");
        if (category is null)
        {
            category = new Category("Test Category");
            await context.Categories.AddAsync(category);
            await context.SaveChangesAsync();
        }

        var product = await context.Products.FirstOrDefaultAsync(x => x.Name == "Test Product");
        if (product is null)
        {
            product = new Product("Test Product", category);
            context.Products.Attach(product);
            await context.SaveChangesAsync();
        }
    }
}

I have instected the state of the entries right before the call to context.SaveChangesAsync() and it seems that the entities are in correct state as below:

When saving Category

When saving Product

What am I missing? Appreciate any hints. pointers.

Thanks

JinishBhardwaj commented 3 years ago

Solved the issue. Reference properties must have a private setter atleast