Entity Framework Core receiving 0 for seeding as Id although I defined it to be something else

1.1k Views Asked by At

When trying to add a migration, ef core gives me the following error:

The seed entity for entity type 'MyEntity' cannot be added because a non-zero value is required for property 'Id'. Consider providing a negative value to avoid collisions with non-seed data.

Entity MyEntity and its superclass/interface:

public class MyEntity: EntityObject
{
    public long OrderId { get; set; }
    public List<StateChange> StateChanges { get; set; }
}

public class EntityObject : IEntityObject
{
    [Key]
    public int Id { get; set; }        
    [Timestamp]
    public byte[] RowVersion
    {
        get;
        set;
    }
}
public class IEntityObject
{
    int Id { get; set; 
    byte[] RowVersion { get; set; }
}

Seeding:

 var seed = new MyEntity{ Id = -2, StateChanges = new List<StateChange>(), OrderId = -2 };
 modelBuilder.Entity<MyEntity>().HasData(seed);

I have been trying various things with the [Key] and [DatabaseGenerated] but could not find a solution. The version of all the ef core nuget dependencies is 3.1.5

2

There are 2 best solutions below

0
On BEST ANSWER

Change the class IEntityObject to an interface.

Here is a fully working console project that assumes that StateChange is some internal class of yours and not a model/entity class:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public interface IEntityObject // <-- changed from `class` to `interface`
    {
        int Id { get; set; }
        byte[] RowVersion { get; set; }
    }

    public class EntityObject : IEntityObject
    {
        [Key]
        public int Id { get; set; }   
        
        [Timestamp]
        public byte[] RowVersion
        {
            get;
            set;
        }
    }

    public class MyEntity : EntityObject
    {
        public long OrderId { get; set; }
        public List<StateChange> StateChanges { get; set; }
    }
        
    public class StateChange
    {
    }
    
    public class Context : DbContext
    {
        public DbSet<MyEntity> MyEntities { get; set; }
        
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So62865284")
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<MyEntity>(
                entity =>
                {
                    entity.Ignore(e => e.StateChanges); // <-- ignore your internal class
                    
                    entity.HasData(
                        new MyEntity
                        {
                            Id = 2,
                            StateChanges = new List<StateChange>(),
                            OrderId = 2
                        });
                });
        }
    }

    internal static class Program
    {
        private static void Main()
        {
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            var myEntitiesWithStateChanges = context.MyEntities
                .OrderBy(i => i.Id)
                .ToList();
            
            Debug.Assert(myEntitiesWithStateChanges.Count == 1);
        }
    }
}

In case StateChange is supposed to be a model class/entity as well, then you should add a foreign key to it and setup the relationship. This could look like this:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public interface IEntityObject // <-- changed from `class` to `interface`
    {
        int Id { get; set; }
        byte[] RowVersion { get; set; }
    }

    public class EntityObject : IEntityObject
    {
        [Key]
        public int Id { get; set; }   
        
        [Timestamp]
        public byte[] RowVersion
        {
            get;
            set;
        }
    }

    public class MyEntity : EntityObject
    {
        public long OrderId { get; set; }
        public List<StateChange> StateChanges { get; set; } = new List<StateChanges>();
    }
        
    public class StateChange : EntityObject
    {
        public int MyEntityId { get; set; } // <-- added FK
        public MyEntity MyEntity { get; set; } // <-- added navigation property
    }
    
    public class Context : DbContext
    {
        public DbSet<MyEntity> MyEntities { get; set; }
        public DbSet<StateChange> StateChanges { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So62865284")
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<MyEntity>(
                entity =>
                {
                    // Added the relationship definition (not really necessary here,
                    // because this would also work by convention).
                    entity.HasMany(e => e.StateChanges)
                        .WithOne(s => s.MyEntity)
                        .HasForeignKey(s => s.MyEntityId);

                    entity.HasData(
                        new MyEntity
                        {
                            Id = 2,
                            OrderId = 2
                        });
                });
            
            modelBuilder.Entity<StateChange>(
                entity =>
                {
                    entity.HasData(
                        new StateChange
                        {
                            Id = 2,
                            MyEntityId = 2,
                        });
                });
        }
    }

    internal static class Program
    {
        private static void Main()
        {
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            var myEntitiesWithStateChanges = context.MyEntities
                .Include(e => e.StateChanges)
                .OrderBy(i => i.Id)
                .ToList();
            
            Debug.Assert(myEntitiesWithStateChanges.Count == 1);
            Debug.Assert(myEntitiesWithStateChanges[0].StateChanges.Count == 1);
        }
    }
}
0
On

I agree with answer provided by lauxjpn. Adding in my two cents on explaining why it is so.

The 'HasData' method does not work well with entities when there are relationships defined between them. Please note that 'HasData' takes in parameters equivalent to the columns created in a table.

So as per the entity design in question, the table created for MyEntity object will not contain the StateChange entity relation data. In fact the StateChange entity will have a MyEntityId column to refer to the Entity object Id in the form of a foreign key.

In order to add the seed data, you have to call in 'HasData' for both entities by making use of anonymous type (used if entity properties and table columns do not match) based upon the table columns generated.

        modelBuilder.Entity<MyEntity>().HasData(
            new
            {
                Id = 1,
                OrderId = 2,
            });

        modelBuilder.Entity<StateChange>().HasData(
            new
            {
                MyEntityId = 1,
                //Fill in remaining properties of State Change
            });

More information on data seeding using 'HasData' can be found on microsoft docs website.

data seeding