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

Auto incrementing primary key with value converter does not work #13814

Closed
codenesium opened this issue Oct 30, 2018 · 3 comments
Closed

Auto incrementing primary key with value converter does not work #13814

codenesium opened this issue Oct 30, 2018 · 3 comments

Comments

@codenesium
Copy link

codenesium commented Oct 30, 2018

I have a primary key in my SQL database declared as

[ADD_ID] [numeric](8, 0) IDENTITY(1,1) NOT NULL.

I'm getting an error that auto incrementing a key doesn't work with a value converter. I want the id in my code to be an int because that's really what is it. I know behind the scenes you're adding a casting converter to map the decimal in the database to the int in the model. Some dumb dumb back in the day declared our primary keys as numeric instead of int. Is there a work around for this short of explicitly going to the database to get top 1 id and adding one to it?

Relevant code in entity

[Column("add_id", TypeName="decimal")]
public int Id { get; private set; }

Relevant code in OnModelCreating

.HasKey(c => new
{
c.Id,
});
modelBuilder.Entity<Address>()
.Property("Id")
.ValueGeneratedOnAdd()
.UseSqlServerIdentityColumn();

I've also tried not explicitly setting the type to decimal but then I just get a decimal to int conversion issue.

System.NotSupportedException
  HResult=0x80131515
  Message=Value generation is not supported for property 'Address.Id' because it has a 'CastingConverter<int, decimal>' converter configured. Configure the property to not use value generation using 'ValueGenerated.Never' or 'DatabaseGeneratedOption.None' and specify explict values instead.
  Source=Microsoft.EntityFrameworkCore
  StackTrace:
   at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorSelector.CreateFromFactory(IProperty property, IEntityType entityType)
   at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorSelector.<Select>b__6_0(IProperty p, IEntityType t)
   at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorCache.<>c.<GetOrAdd>b__3_0(CacheKey ck)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorCache.GetOrAdd(IProperty property, IEntityType entityType, Func`3 factory)
   at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorSelector.Select(IProperty property, IEntityType entityType)
   at Microsoft.EntityFrameworkCore.SqlServer.ValueGeneration.Internal.SqlServerValueGeneratorSelector.Select(IProperty property, IEntityType entityType)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ValueGenerationManager.Generate(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Nullable`1 forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node, Boolean force)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode node, TState state, Func`3 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState entityState, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.Add[TEntity](TEntity entity)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
   at TylerTech.TPS.TPSAPINS.Api.DataAccess.AbstractAddressRepository.<Create>d__9.MoveNext() in C:\Users\Administrator\Documents\Mercurial\TPS\NET\TPSApiService\TPSAPI.Api.DataAccess\Repositories\AbstractAddressRepository.g.cs:line 41

Steps to reproduce

Repo to reproduce
https://github.com/codenesium/tmp

Further technical details

EfCore.SqlServer 2.1.3
Database Provider:
Operating system:
Windows

@ajcvickers
Copy link
Member

Duplicate of #11597. @codenesium For workarounds, you could try #11970 (comment)

@codenesium
Copy link
Author

codenesium commented Oct 31, 2018

@ajcvickers I looked at 11970 and it almost works. Do you see anything I've done here that looks wrong? I'm getting a typecast exception. I'm converting the values to strings out of desperation. I can't tell from which part the exception comes from.

var generator = new InMemoryIntegerValueGenerator<int>();
var converter = new ValueConverter<int, decimal>(
v => decimal.Parse(v.ToString()),
v => Convert.ToInt32(v.ToString()),
new ConverterMappingHints(valueGeneratorFactory: (p, t) => generator));


modelBuilder
	.Entity<Address>()
	.Property(p => p.Id)
	.HasConversion(converter);


Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updatin
g the entries. See the inner exception for details. ---> System.InvalidOperation
Exception: An exception occurred while reading a database value for property 'Ad
dress.Id'. The expected type was 'System.Int32' but the actual value was of type
 'System.Decimal'. ---> System.InvalidCastException: Unable to cast object of ty
pe 'System.Decimal' to type 'System.Int32'.
   at System.Data.SqlClient.SqlBuffer.get_Int32()
   at System.Data.SqlClient.SqlDataReader.GetInt32(Int32 i)
   at lambda_method(Closure , DbDataReader )
   --- End of inner exception stack trace ---

@ajcvickers
Copy link
Member

@codenesium I was able to make this work:

public class BloggingContext : DbContext
{
    private static readonly LoggerFactory Logger
        = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseLoggerFactory(Logger)
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        var converter = new ValueConverter<int, decimal>(
            v => v,
            v => (int)v,
            new ConverterMappingHints(valueGeneratorFactory: (p, t) => new TemporaryIntValueGenerator()));

        modelBuilder.Entity<Address>()
            .Property("Id")
            .ValueGeneratedOnAdd()
            .UseSqlServerIdentityColumn()
            .HasConversion(converter);
    }
}

public class Address
{
    [Column("add_id", TypeName = "decimal")]
    public int Id { get; private set; }
}

public class Program
{
    public static void Main()
    {
        using (var context = new BloggingContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            context.AddRange(new Address(), new Address());

            context.SaveChanges();
        }

        using (var context = new BloggingContext())
        {
            var addresses = context.Set<Address>().ToList();
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants