Search code examples
entity-framework-coreef-code-first

EfCore OwnsOne seed nullable objects failing


I want to seed data with EfCore to my Owned Entities who can be nullable

Entities:

public class RootEntity
{
    protected RootEntity() { }

    public Guid Id { get; set; }

    public OwnedEntityLevel1? OwnedEntityLevel1 { get; set; } // can be nullable
}

public class OwnedEntityLevel1
{
    public Guid Id { get; set; }
}

Model configuration for DbContext:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<RootEntity>(b =>
    {
        b.OwnsOne(x => x.OwnedEntityLevel1, ob =>
        {
            ob.HasData(RootEntity.All.Select(x => new
                { x.OwnedEntityLevel1?.Id, RootEntityId = x.Id }));
        });
        b.HasData(RootEntity.All.Select(x => new { x.Id }));
    });
}

When i try to create my migration with:

dotnet ef migrations add Initial --context NullableObjectDbContext -o Migrations/NullableObject

i receive the error:

The seed entity for entity type 'OwnedEntityLevel1' cannot be added because no value was provided for the required property 'Id'.

The message oviously is correct. But i do not understand if you could seed nullable objects with .HasData somehow?

The data i am trying to seed:

public static RootEntity PredefinedEntity11 { get; } =
    new(
        Guid.Parse("96e1d442-bdd0-4c6f-9d01-624b27abbac3"),
        new OwnedEntityLevel1
        {
            Id = Guid.Parse("8f8eea73-0b43-412a-b0aa-a9338db6e067")
        }
    );

public static RootEntity PredefinedEntity12 { get; } =
    new(
        Guid.Parse("aae51dac-016e-472e-ad51-2f09f8cb9fbb"),
        null! // When i add this the migration fails with The seed entity for entity type 'OwnedEntityLevel1' cannot be added because no value was provided for the required property 'Id'
    );

public static IReadOnlyList<RootEntity> All { get; } =
    new List<RootEntity>(new[] { PredefinedEntity11, PredefinedEntity12 }).AsReadOnly();

I my normal program flow i can add nullable objects without a problem:

var ctx = new NullableObjectDbContext();
var rootEntity = new RootEntity(Guid.NewGuid(), null);
ctx.Add(rootEntity);
ctx.SaveChanges();

I have created a minimal reproducible example here: https://github.com/enterprisebug/EfCoreHasDataNestedOwnedTypes/tree/main/EfCoreHasDataNestedOwnedTypes/NullableObject


Solution

  • Model data seeding with anonymous types matches properties by both name and type.

    In your case, even though the seeding type has property called Id, its type is different from the type of the Id property of the seeded entity (Nullable<Guid> inferred from ?. operator vs Guid), hence is not mapped and is generating the confusing error message.

    new
    { 
        x.OwnedEntityLevel1?.Id, // Guid? Id
        RootEntityId = x.Id      // Guid RootEntityId 
    }
    

    The solution is to generate and populate a Guid Id property in the anonymous type by first filtering out null objects, e.g. (null forgiving operator is used to suppress the NRT warning):

    ob.HasData(RootEntity.All
        .Where(x => x.OwnedEntityLevel1 != null)
        .Select(x => new
        { 
            x.OwnedEntityLevel1!.Id, // Guid Id
            RootEntityId = x.Id      // Guid RootEntityId
        }));