Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQLite Error 19: 'UNIQUE constraint failed' when using same DbConnection different DbContext instances #15819

Closed
wtorricos opened this issue May 27, 2019 · 2 comments · Fixed by #16559
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@wtorricos
Copy link

wtorricos commented May 27, 2019

Sqlite throws Microsoft.Data.Sqlite.SqliteException : SQLite Error 19: 'UNIQUE constraint failed:
when you use the same connection in different instances of DbContext and you have a relationship of 0 to 1.

Here is a sample project with 3 unit tests where the problem can be reproduced:
https://github.com/tico321/SQLiteUniqueErrorSeparateUsings/blob/master/SQLiteUniqueError/UnitTest1.cs

Also note that the problem only happens when there are already stored entities in this case BEntities.

We can see that if we run the test using the same DbContext it will pass:

        [Fact]
        public void SuccessRunningInSameUsing()
        {
            var connection = new SqliteConnection("DataSource=:memory:");
            connection.Open();

            try
            {
                var options = new DbContextOptionsBuilder<MyContext>().UseSqlite(connection).Options;

                using (var context = new MyContext(options))
                {
                    context.Database.EnsureCreated();
                    //init data
                    context.BEntities.Add(new EntityB { EntityBId = 1 });
                    context.SaveChanges();

                    context.Add(new EntityA { EntityAId = 1, EntityB = new EntityB { EntityBId = 1 } });
                    context.SaveChanges();
                }
            }
            finally
            {
                connection.Close();
            }
        }

Also weird but a workaround is that if we load the BEntities in the second using the test will pass:

       [Fact]
       public void SuccessLoadingBEntities()
       {
           var connection = new SqliteConnection("DataSource=:memory:");
           connection.Open();

           try
           {
               var options = new DbContextOptionsBuilder<MyContext>()
                   .UseSqlite(connection)
                   .Options;

               using (var context = new MyContext(options))
               {
                   context.Database.EnsureCreated();
                   //init data
                   context.BEntities.Add(new EntityB { EntityBId = 1 });
                   context.SaveChanges();
               }

               using (var context = new MyContext(options))
               {
                   context.BEntities.ToList();//workaround to avoid Unique error

                   context.Add(new EntityA { EntityAId = 1, EntityB = new EntityB { EntityBId = 1 } });
                   context.SaveChanges();
               }
           }
           finally
           {
               connection.Close();
           }
       }

The issue is that the test will fail without the workaround where we list BEntities and will throw the Unique Constraint failed error.

Further technical details

EF Core version: 2.2.
Microsoft.EntityFrameworkCore.Sqlite: 2.2.4
Operating system: Windows 10
IDE: Visual Studio 2019

@ajcvickers
Copy link
Member

ajcvickers commented May 27, 2019

@tico321 Given that the code above creates two EntityB instances with the same ID, I would expect either EF or the database to throw here indicating that two entities with the same primary key cannot be inserted. Can you provide some more details as to why you don't expect this?

Note for triage: full repro code below. Somehow EF ends up using the tracked entity instance instead of the new entity instance.

public class EntityB
{
    public int EntityBId { get; set; }
    public EntityA EntityA { get; set; }
}

public class EntityA
{
    public int EntityAId { get; set; }
    public int? EntityBId { get; set; }
    public EntityB EntityB { get; set; }
}

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<EntityA> AEntities { get; set; }
    public DbSet<EntityB> BEntities { get; set; }
}

public class Program
{
    public static void Main()
    {
        var connection = new SqliteConnection("DataSource=:memory:");
        connection.Open();

        try
        {
            var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlite(connection)
                .Options;

            using (var context = new BloggingContext(options))
            {
                context.Database.EnsureCreated();
                context.BEntities.Add(new EntityB { EntityBId = 1 });
                context.SaveChanges();
            }

            using (var context = new BloggingContext(options))
            {
                var b1 = context.BEntities.ToList().Single();
                var b2 = new EntityB { EntityBId = 1 };
                
                context.Add(new EntityA { EntityAId = 1, EntityB = b2 });
                context.SaveChanges();

                var bs = context.BEntities.Local.ToList(); // Only one b, and it's b1!
            }
        }
        finally
        {
            connection.Close();
        }
    }
}

@wtorricos
Copy link
Author

@ajcvickers Thank you for your response.

You made me realize it was my mistake. Basically I was expecting this to work:

            using (var context = new BloggingContext(options))
            {
                var b2 = new EntityB { EntityBId = 1 };
                
                context.Add(new EntityA { EntityAId = 1, EntityB = b2 });
                context.SaveChanges();

                var bs = context.BEntities.Local.ToList(); // Only one b, and it's b1!
            }

As equivalent to this:

            using (var context = new BloggingContext(options))
            {
                var b2 = new EntityB { EntityBId = 1 };
                // I realized it's needed to attach it so EF won't try to create a new one
                context.Bentities.Attach(b2);                 

                context.Add(new EntityA { EntityAId = 1, EntityB = b2 });
                context.SaveChanges();
            }

And after your example we can see that we can accomplish the same result by loading the entity, and EF will use the tracked one....

Thanks again you for your time and help!

@ajcvickers ajcvickers reopened this May 27, 2019
@ajcvickers ajcvickers self-assigned this May 28, 2019
@ajcvickers ajcvickers added this to the 3.0.0 milestone May 28, 2019
@ajcvickers ajcvickers modified the milestones: 3.0.0, 3.0.0-preview7 Jul 2, 2019
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jul 10, 2019
ajcvickers added a commit that referenced this issue Jul 10, 2019
Fixes #15819

We had previously decided in #1246 on a behavior for resolving this, but that behavior results in not tracking the graph passed to us. On reflection, that decision was likely wrong, so reverting it here to avoid discarding entities in the graph.
ajcvickers added a commit that referenced this issue Jul 11, 2019
Fixes #15819

We had previously decided in #1246 on a behavior for resolving this, but that behavior results in not tracking the graph passed to us. On reflection, that decision was likely wrong, so reverting it here to avoid discarding entities in the graph.
ajcvickers added a commit that referenced this issue Jul 11, 2019
Fixes #15819

We had previously decided in #1246 on a behavior for resolving this, but that behavior results in not tracking the graph passed to us. On reflection, that decision was likely wrong, so reverting it here to avoid discarding entities in the graph.
@ajcvickers ajcvickers modified the milestones: 3.0.0, 3.0.0-preview8 Jul 29, 2019
@ajcvickers ajcvickers modified the milestones: 3.0.0-preview8, 3.0.0 Nov 11, 2019
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants