Skip to content

How to Get Case Sensitive Primary Keys #4654

@SudoWatson

Description

@SudoWatson

I'm using EF Core for a personal project that include getting entities from a public API and saving these entities to my database. The API returns these entities with unique IDs that are case sensitive, meaning 2 different entities can have the same ID only different by casing. Since the API already generates these unique IDs and uses them for relational information, I wanted to use these as the PK of my database entities. After about 15 hours of debugging I found out EF and SQL don't by default are case insensitive. I would like to somehow get EF to recognize these columns as case-SENSITIVE. I found collations and tried implementing them but seem to be missing some steps as EF doesn't recognize the collation.

Here I've got just a basic entity, where it's primary key should be case-sensitive (as denoted by the custom case-sensitive attribute I created)

public class CaseSensitiveEntity {
    [Key]
    [CaseSensitive]
    public string NamePK { get; set; }
}

This is the attribute, it just exists

public class CaseSensitiveAttribute : Attribute {}

Here's my DbContext. In the OnModelCreating method I'm checking if a property has the attribute and apply the case sensitive collation to it if it does.

public class TestDbContext : DbContext {
    public DbSet<CaseSensitiveEntity> CaseSensitiveEntities { get; set; }
    public TestDbContext() : base() { }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
        optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=CaseSensitive;Trusted_Connection=True;MultipleActiveResultSets=true");
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        base.OnModelCreating(modelBuilder);
        foreach (var entityType in modelBuilder.Model.GetEntityTypes()) {
            foreach (var property in entityType.GetProperties()) {
                // If the property is decorated with the CaseSensitive attribute then set the collation to be case sensitive
                if (property.PropertyInfo?.GetCustomAttribute<CaseSensitiveAttribute>() != null) {
                    property.SetCollation("SQL_Latin1_General_CP1_CS_AS");
                }
            }
        }
    }
}

I can tell this at least attempts to apply the collation by looking at the migration file

migrationBuilder.CreateTable(
    name: "CaseSensitiveEntities",
    columns: table => new
    {
        NamePK = table.Column<string>(type: "nvarchar(450)", nullable: false, collation: "SQL_Latin1_General_CP1_CS_AS")
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_CaseSensitiveEntities", x => x.NamePK);
    });

In my program file I try adding 2 CaseSensitiveEntities, both with the same key only different by case:

using (TestDbContext dc = new TestDbContext()) {
    CaseSensitiveEntity cse1 = new CaseSensitiveEntity() {NamePK="CaseSensitive" };
    CaseSensitiveEntity cse2 = new CaseSensitiveEntity() {NamePK="CASESensitiVE" };
    dc.CaseSensitiveEntities.Add(cse1);
    dc.CaseSensitiveEntities.Add(cse2);  // <-- This is C:\Users\standarduser\source\repos\CaseSensitive\CaseSensitive\Program.cs:line 7
}

On the final line, when attempting to add the second entity (cse2), an exception is thrown because EF thinks they have the same key:

System.InvalidOperationException: 'The instance of entity type 'CaseSensitiveEntity' cannot be tracked because another 
instance with the same key value for {'NamePK'} is already being tracked. When attaching existing entities, ensure that 
only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' 
to see the conflicting key values.'

Stack trace:

   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NullableKeyIdentityMap`1.Add(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey, Nullable`1 fallbackState)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.SetEntityState(InternalEntityEntry entry, EntityState entityState)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
   at Program.<Main>$(String[] args) in C:\Users\standarduser\source\repos\CaseSensitive\CaseSensitive\Program.cs:line 7

What else am I missing to be able to use case sensitive primary keys in EF core?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions