2

I think it's something wrong with the model in the example below, but I can't figure out what.

In the example I have a Container class with Items containing sub Items. As soon as I try to create a container with more than one level of Items it looses the relationship with the container and therefore fails due to foreign key constraints.

The exception I get is:

Microsoft.EntityFrameworkCore.DbUpdateException : An error occurred while updating the entries. See the inner exception for details. ---- System.Data.SqlClient.SqlException : The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Items_Containers_ContainerId". The conflict occurred in database "Test", table "dbo.Containers", column 'Id'. The statement has been terminated.

When I look at the SQL that EF Core generates, using SQL Profiler, it tries to insert 0 as id for the Container for all non direct children.

So, creating the Container is not a problem, nor is the first level children, but as soon as I add a second level it looses the relationship with the Container.

public class Test
{
    public class Container
    {
        [Required]
        public int Id { get; set; }
        public IEnumerable<Item> Items { get; set; }
    }

    public class Item
    {
        [Required]
        public int Id { get; set; }
        [Required]
        public int ContainerId { get; set; }
        public virtual Container Container { get; set; }
        public int? ParentItemId { get; set; }
        public virtual Item ParentItem { get; set; }
        public IEnumerable<Item> ChildItems { get; set; }
    }

    public class TestContext : DbContext
    {
        public virtual DbSet<Container> Containers { get; set; }
        public virtual DbSet<Item> Items { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Container>()
                .HasMany(c => c.Items)
                .WithOne(c => c.Container)
                .HasForeignKey(c => c.ContainerId);

            modelBuilder.Entity<Item>()
                .HasOne(x => x.Container)
                .WithMany(x => x.Items)
                .HasForeignKey(x => x.ContainerId);

            modelBuilder.Entity<Item>()
                .HasOne(x => x.ParentItem)
                .WithMany(x => x.ChildItems)
                .HasForeignKey(x => x.ParentItemId);
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder.UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=Test;Trusted_Connection=True;ConnectRetryCount=0");

    }

    public void ContextTest()
    {
        using (var context = new TestContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            var container = new Container();

            container.Items = new List<Item>
            {
                new Item
                {
                    ChildItems = new List<Item>
                    {
                        new Item()
                    }
                }
            };

            context.Containers.Add(container);

            context.SaveChanges();
        }
    }
}
Ganhammar
  • 1,871
  • 1
  • 16
  • 29
  • Possible duplicate of [INSERT statement conflicted with the FOREIGN KEY constraint - SQL Server](https://stackoverflow.com/questions/2965837/insert-statement-conflicted-with-the-foreign-key-constraint-sql-server) Possible duplicate of [update](https://stackoverflow.com/q/24214860/4977870) – Anas Alweish Oct 25 '18 at 06:10

1 Answers1

4

When you add Container instance to the context, the EF Core relationship fixup process will examine the Items collection and will automatically assign Item.Container property (and the FK). However the lower level items and neither contained in Items collection nor have Container property assigned, so EF will try to use whatever value FK contains (since it's non nullable, it will use the 0 - note that 0 is valid value for non generated key).

If you are wondering why it isn't assigning the top Container recursively, the answer is - because the model does not imply such behavior. From relational standpoint there is no relation between ParentItem.Container and ChildItem.Container - it's pretty valid they to have different values. If the intention is that all child items share the root item container, then the entity model contains redundancy - Container property/FK must be nullable and assigned only for the root items (basically mutually exclusive with ParentItem).

If you want to keep it the way it is, there is no way to express your intent to EF Core (or relational database in general). So you need to enforce that constraint manually by either adding lower level items to the container Items collection, or the easier - assign container instance to their Container property:

var container = new Container();
container.Items = new List<ContainerItem>
{
    new ContainerItem
    {
        ChildItems = new List<ContainerItem>
        {
            new ContainerItem
            {
                Container = container // <-- do the same for all non direct items
            }
        }
    }
};
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Thank you, what you say make very much sense! The only thing I'm a bit curious too is the fact that this works with EF but not EF Core. Do you know why the behaviour is different? – Ganhammar Oct 25 '18 at 14:28
  • 1
    They have for sure many differences in behaviors. But I will be quite surprised if specifically this works in EF6. Will check when have some time, although there is nothing we can do - it is what it is. – Ivan Stoev Oct 25 '18 at 14:55
  • 1
    What I'm currently working on is migrating a project from ef6 to core, and this is one of the things I ran into.. – Ganhammar Oct 25 '18 at 16:27