-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
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?