How to create Autoincrement column in SQLite using EF core?
Asked Answered
D

4

23

I am using Entity Framework Core 2.0 for Sqlite code first in my UWP and .NET Standard app. My model has an entity of type Primary Key integer which should be served as auto increment according to SQLite documentation. But in real for every row that Identity column is getting 0 as a value. Please help because i found no helping material related to this issue.

This is my property without any data annotaion.

public Int32 No { get; set; }

I have used fluent API

modelBuilder.Entity<TurnosGeneral>()
            .HasKey(c => new { c.No, c.Cod_Turno });

And value is inserted here

db.TurnosGenerals.Add(new TurnosGeneral { Cod_Turno = numeroTurnoTextBlock.Text });

db.SaveChanges();

For every row inserted c.No is 0.

Delphinium answered 31/3, 2018 at 20:49 Comment(0)
S
27

My model has an entity of type Primary Key integer which should be served as auto increment

The problem is that the property in question is not a PK, but a part of a composite PK, in which case it's not considered as auto generated by convention, as explained in the Generated Values Conventions section of the EF Core documentation:

By convention, non-composite primary keys of type short, int, long, or Guid will be setup to have values generated on add. All other properties will be setup with no value generation.

You need to specify that explicitly:

modelBuilder.Entity<TurnosGeneral>()
    .Property(e => e.No)
    .ValueGeneratedOnAdd();

Update: The above is the general approach applicable for most of the databases. But SQLite supports AutoIncrement only for column of type INTEGER PRIMARY KEY, hence this is not EF Core limitation. Either don't use auto increment or make it non-composite PK.

Sasser answered 1/4, 2018 at 4:6 Comment(10)
I have already tried this and always got an error of " SqliteException: SQLite Error 19: 'NOT NULL constraint failed: " Which means instead of auto generation its expecting user generated values on Add. No doubt this works good with sql server in EF 6.Delphinium
It's working good with SqlServer in EF Core 2.0.2 as well. Can't find SQLite specific requirement in documentation.Sasser
Ok, I've installed [SQLite EF Core Database Provider)(learn.microsoft.com/en-us/ef/core/providers/sqlite) and got this migration (note Sqlite:Autoincrement): migrationBuilder.CreateTable(name: "TurnosGeneral", columns: table => new { No = table.Column<int>(nullable: false).Annotation("Sqlite:Autoincrement", true), Cod_Turno = table.Column<string>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_TurnosGeneral", x => new { x.No, x.Cod_Turno }); });Sasser
But you are right, there seem to be a bug in SQLite provider migration generator which doesn't take that annotation into account. Consider posting an issue in their issue tracker.Sasser
Annotation("Sqlite:Autoincrement", true) is also not working already tried. BTW thanks for your comments. It seems i have to create new issue on github for EF Core for SQLite.Delphinium
You are welcome. I've just looked at their SqliteMigrationsSqlGenerator code, it appends "AUTOINCREMENT" only for columns which are non-composite PK :(Sasser
But looks like this is actually SQLite requirementSasser
Yes its SQLite requirement and now SQLite is officially maintained by Microsoft.Delphinium
The point is, if database does not support it, there is nothing EF Core can do. So we :)Sasser
I can confirm the behavior is still the same, i.e. I do not know how to surpass this with sqlite, i.e. generate an id integer which is not PKSaez
B
4

Looks like a bug. Check this : https://github.com/aspnet/EntityFrameworkCore/issues/11961

My workaround: Manually change : .ValueGeneratedNever() to .ValueGeneratedOnAdd() on Id columns in my DBContext class.

Braynard answered 25/8, 2018 at 8:49 Comment(2)
I got this same issue with version 7.0.4 of entity framework. Changing .ValueGeneratedNever() to .ValueGeneratedOnAdd() fixed the issue. Looks like the bug is back.Proceeding
I'm not able to reproduce the bug consistently when scaffolding so not sure what causes it.Proceeding
R
4

I just ran into this with a SQLite in memory database I was using for testing. In my case I had a business class with a primary key field called ContactCategoryId:

public class ContactCategory
{
    [Required]
    [Display(Name = "Contact category ID")]
    public int ContactCategoryId { get; set; }

And I was also using the fluent approach for the settings:

public void Configure(EntityTypeBuilder<ContactCategory> modelBuilder)
{
    modelBuilder.ToTable("CONTACT_CATEGORY", _schema);

    modelBuilder.HasKey(x => x.ContactCategoryId);

    modelBuilder.Property(x => x.ContactCategoryId)
        .HasColumnName(@"ContactCategoryID")
        //.HasColumnType("int") Weirdly this was upsetting SQLite
        .IsRequired()
        .ValueGeneratedOnAdd()
        ;

Commenting out the line for .HasColumnType("int") fixed the error for me.

Roughneck answered 5/4, 2019 at 13:45 Comment(1)
For me it worked only after I completely removed entity.Property(e => e.Id).HasColumnName("ID");Predestination
C
0

This is a very dirty hack, depends a lot on your requirements, and obviously it should never use in production. It has some limitations: for example all the inserts must be made using E.F... but maybe it useful for someone.

In my case: I have a lot of integration tests, I can run it using an in memory Sqlite database and sometimes using the real database. To separate the production and test code I have all the code in a new DbContext that inherits from my real DbContext, in the test I inject this new DbContext.

The code: override the SaveChanges methos and call you own code.

public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
    ProcessSqliteIdentities(ChangeTracker.Entries());
    return base.SaveChanges(acceptAllChangesOnSuccess);
}

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    ProcessSqliteIdentities(ChangeTracker.Entries());
    return base.SaveChangesAsync(cancellationToken);
}

The ProcessSqliteIdentities function seeds the identity columns (and saves the last identity for each entity)

//Dictionary to save the last identity for each entity type
private readonly ConcurrentDictionary<Type, int> SqliteIdentities = new();

private void ProcessSqliteIdentities(IEnumerable<EntityEntry> entries)
{
    //Just in case
    if (Database.ProviderName != "Microsoft.EntityFrameworkCore.Sqlite")
        return;

    entries
        .Where(e => e.State == EntityState.Added)
        .Select(e => new
        {
            e.Entity,
            NewIdentity = SqliteIdentities.AddOrUpdate(
                e.Entity.GetType(),
                1,
                (_, currentIdentity) => currentIdentity + 1)
        })
        .Select(e => e.Entity switch
        {
            Customer c => c.CustomerId = e.NewIdentity,
            Book b => b.BookId = e.NewIdentity,
            // Add one line for each of you database types
            _ => (object)null! //Just in case you have one entity with no multiple primary keys
        })
        .ToList(); //Or whatever method you like to force the enumeration of the select
}

I hope it helps someone :)

Cupriferous answered 7/5, 2023 at 19:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.