EF Core - duplicate entry in table index when adding record to table with composite keys - entity-framework-core

I have a class with a composite key. When I try to save a record where a part of that key is contained in another record's key, I am getting an exception although the composite keys as a whole are unique.
Data type with navigation:
public class ProxyInfo
{
[Key, Column(Order = 0, TypeName = "varchar")]
[StringLength(80)]
public string AccountID { get; set; } = string.Empty;
[Key, Column(Order = 1, TypeName = "varchar")]
[StringLength(80)]
public string ProxyID { get; set; } = string.Empty;
[ForeignKey(nameof(ProxyID))]
public virtual UserInfo? Proxy { get; set; }
// a few more properties
}
OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProxyInfo>()
.HasKey(e => new { e.AccountID, e.ProxyID })
.HasName("PK_ProxyInfo");
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
// more code follows
}
Here's the relevant controller code:
[Route(Routes.Base + "[controller]")]
[ApiController]
public class ProxyInfoApiController : ControllerBase
{
private readonly DS2DbContext _context;
public ProxyInfoApiController(DS2DbContext context)
{
_context = context;
}
[HttpPost("Add")]
[AllowAnonymous]
public async Task<IActionResult> Add([FromBody] ProxyInfo proxyInfo)
{
try
{
_context.ProxyInfo.Add(proxyInfo);
await _context.SaveChangesAsync();
}
catch (Exception e)
{
ServerGlobals.Logger?.Error($"ProxyInfoController.Add: Error '{e.Message}' occurred.");
}
return Ok(); // Created(proxyInfo.ID.ToString(), proxyInfo);
}
}
The error message reads:
A row with a duplicate key cannot be inserted in the dbo.ProxyInfo
object with the unique IX_ProxyInfo_ProxyID-Index. The duplicate
key value is "X".
The complex key I tried to insert was {"B","X"}. The only other record in the ProxyInfo table has key {"A", "X"}; So there should be two different ProxyInfo records referencing the same UserInfo record.
The problem seems to be that an index is being tried to be updated with a value it already contains. However, the indices of both records can be identical, as multiple ProxyInfos can reference the same UserInfo. So actually, no duplicate entry is created. It's just that a 2nd ProxyInfo record uses the same user as the 1st one.
I just found out that the relevant index is created during initial migration with a unique:true attribute. The question is whether I can make EF Core skip updating the index when it already contains an index that it is trying to add again.

I found the problem. It was this statement:
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithOne()
.OnDelete(DeleteBehavior.NoAction);
which should have been
modelBuilder.Entity<ProxyInfo>()
.HasOne(p => p.Proxy)
.WithMany()
.OnDelete(DeleteBehavior.NoAction);
What I also didn't know is that when I make changes like this one, I need to create and execute a migration. Once I did that, the index got changed into a non unique one.
I am only doing EF Core/Blazor for a year and I am dealing more with application development than with the framework around it, so this is all new to me and it took me a while to figure it out.

Related

EF Core 5 adds shadow alternate key to some entities but does not use the property

UPDATED: The sample code listed below is now complete and sufficient
to generate the shadow alternate key in Conference. When the Meeting
entity inherits from a base entity containing a RowVersion attribute
the shadow alternate key is generated in the Conference entity.
If that attribute is included directly in the Meeting entity,
without inheritance, the shadow alternate key is not generated.
My model worked as expected in EF Core 3.1. I upgraded to .Net 5 and EF Core 5, and EF adds shadow alternate key attribute(s) named TempId to several entities. EF can't load those entities unless I add those attributes to the database. The shadow alternate key properties are NOT used in any relationships that I can find in the model. Virtually all discussion of shadow properties is either for foreign keys or hidden attributes. I can't find any explanation for why EF would add a shadow alternate key, especially if it doesn't use the attribute. Any suggestions?
One of the entities that gets a shadow alternate key is Conference, which is the child in one relationship and the parent in another. I have many similar entities which do NOT get a shadow alternate key, and I cannot see any difference between them.
I loop through the model entities identifying all shadow properties and all relationships using an alternate key for the principal key. None of the shadow alternate keys are used in a relationship. I do see the two defined relationships where I specifically use an alternate key, so I believe my code is correct.
Here is a complete simplified EF context and its two entities which demonstrates the problem.
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EFShadow
{
public partial class Conference
{
public Conference()
{
Meetings = new HashSet<Meeting>();
}
[Key]
public string ConferenceCode { get; set; }
[Required]
public string ConferenceName { get; set; }
public ICollection<Meeting> Meetings { get; }
}
public partial class Meeting : BaseEntity
{
public Meeting() { }
[Key]
public int MeetingId { get; set; }
[Required]
public string ConferenceCode { get; set; }
[Required]
public string Title { get; set; }
public Conference Conference { get; set; }
}
[NotMapped]
public abstract partial class BaseEntity
{
[Timestamp]
public byte[] RowVersion { get; set; }
}
public class EFShadowContext : DbContext
{
public EFShadowContext(DbContextOptions<EFShadowContext> options)
: base(options)
{
ChangeTracker.LazyLoadingEnabled = false;
}
public DbSet<Conference> Conferences { get; set; }
public DbSet<Meeting> Meetings { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Conference>(entity =>
{
entity.HasKey(e => e.ConferenceCode);
entity.ToTable("Conferences", "Settings");
entity.Property(e => e.ConferenceCode)
.IsRequired()
.HasMaxLength(25)
.IsUnicode(false)
.ValueGeneratedNever();
entity.Property(e => e.ConferenceName)
.IsRequired()
.HasMaxLength(100);
});
builder.Entity<Meeting>(entity =>
{
entity.HasKey(e => e.MeetingId);
entity.ToTable("Meetings", "Offerings");
entity.Property(e => e.ConferenceCode).HasMaxLength(25).IsUnicode(false).IsRequired();
entity.Property(e => e.Title).HasMaxLength(255).IsRequired();
//Inherited properties from BaseEntityWithUpdatedAndRowVersion
entity.Property(e => e.RowVersion)
.IsRequired()
.IsRowVersion();
entity.HasOne(p => p.Conference)
.WithMany(d => d.Meetings)
.HasForeignKey(d => d.ConferenceCode)
.HasPrincipalKey(p => p.ConferenceCode)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("Meetings_FK_IsAnOccurrenceOf_Conference");
});
}
}
}
Here is the code I use to identify the shadow key.
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace ConferenceEF.Code
{
public class EFModelAnalysis
{
readonly DbContext _context;
public EFModelAnalysis(DbContext context)
{
Contract.Requires(context != null);
_context = context;
}
public List<string> ShadowProperties()
{
List<string> results = new List<string>();
var entityTypes = _context.Model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
var entityProperties = entityType.GetProperties();
foreach (var entityProperty in entityProperties)
{
if (entityProperty.IsShadowProperty())
{
string output = $"{entityType.Name}.{entityProperty.Name}: {entityProperty}.";
results.Add(output);
}
}
}
return results;
}
public List<string> AlternateKeyRelationships()
{
List<string> results = new List<string>();
var entityTypes = _context.Model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
foreach (var fk in entityType.GetForeignKeys())
{
if (!fk.PrincipalKey.IsPrimaryKey())
{
string output = $"{entityType.DisplayName()} Foreign Key {fk.GetConstraintName()} " +
$"references principal ALTERNATE key {fk.PrincipalKey} " +
$"in table {fk.PrincipalEntityType}.";
results.Add(output);
}
}
}
return results;
}
}
}
Here is the context initialization and processing code.
var connectionSettings = ((LoadDataConferencesSqlServer)this).SqlConnectionSettings;
DbContextOptionsBuilder builderShadow = new DbContextOptionsBuilder<EFShadowContext>()
.UseSqlServer(connectionSettings.ConnectionString);
var optionsShadow = (DbContextOptions<EFShadowContext>)builderShadow.Options;
using EFShadowContext contextShadow = new EFShadowContext(optionsShadow);
EFModelAnalysis efModelShadow = new EFModelAnalysis(contextShadow);
var shadowPropertiesShadow = efModelShadow.ShadowProperties();
foreach (var shadow in shadowPropertiesShadow)
progressReport?.Report(shadow); //List the shadow properties
var alternateKeysShadow = efModelShadow.AlternateKeyRelationships();
foreach (var ak in alternateKeysShadow)
progressReport?.Report(ak); //List relationships using alternate key
The output I get is:
EFShadow.Conference.TempId: Property: Conference.TempId (no field, int) Shadow Required AlternateKey AfterSave:Throw.
No relationship uses this alternate key.
If I eliminate the Meeting entity's inheritance from BaseEntity and include the RowVersion timestamp property directly in Meeting, no shadow key is generated. That's the only change required to make the difference.
Tricky confusing issue, worth reporting it to EF Core GitHub issue tracker.
Using trial and error approach, looks like the strange behavior is caused by the [NotMapped] data annotation applied to the base class.
Remove it from there (and all other similar places) and the problem is solved. In general don't apply that attribute on model classes. Normally you don't need to explicitly mark a class as "non entity" if its is not referenced by navigation property, DbSet or Entity<>() fluent call. And if you really want to make sure explicitly it isn't used as entity, use Ignore fluent API instead, because the attribute breaks the default conventions which are applied before OnModelCreating.
e.g.
//[NotMapped] <-- remove
public abstract partial class BaseEntity
{
[Timestamp]
public byte[] RowVersion { get; set; }
}
and optionally
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Ignore<BaseEntity>(); // <-- add this
// the rest...
}

Update object when using entity splitting - code first

I had a class called Document, which I split into two entities, in order to separate an expensive binary field:
[Table("Document")]
public class Document
{
[Key]
public int Id { get; set; }
... other fields ...
[Required]
public virtual DocumentBinary DocumentBinary { get; set; }
}
[Table("Document")]
public class DocumentBinary
{
[Key, ForeignKey("Document")]
public int DocumentId { get; set; }
public Document Document { get; set; }
public byte[] DocumentData { get; set; }
}
So, everything works fine, both entities share the same database table and DocumentData is only loaded when it's needed.
However, when it comes to updating the Document entity, I get an error stating that 'DocumentBinary is required'.
When I remove the [Required] attribute from DocumentBinary virtual property, I get the following error:
The entity types 'Document' and 'DocumentBinary' cannot share table 'Documents' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them.
I can obviously do something like:
var test = document.DocumentBinary;
before updating the document object:
documentRepository.Update(document);
This will then load the binary data on my request and save the changes without any issues, but the whole point is that I shouldn't need to do that.
This can be achieved using the fluent API. If you remove the data annotations and in your OnModelCreating add this, it should work.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Document>().HasRequired(d => d.DocumentBinary).
WithRequiredDependent(db => db.Document);
}
I managed to resolve it by overriding my Update method in DocumentRepository:
public override void Update(Document document)
{
try
{
DataContext.Entry(document.DocumentBinary).State = EntityState.Modified; // added this line
DataContext.Entry(document).State = EntityState.Modified;
}
catch (System.Exception exception)
{
throw new EntityException("Failed to update document");
}
}
I know it probably does the same thing as me evaluating DocumentBinary by assigning it to 'test' variable, but it looks like a much cleaner solution.

"A dependent property in a ReferentialConstraint is mapped to a store-generated column" with Id change

Situation
I have searched for the answer to this extensively (on SO and elsewhere) and I am aware that there are many questions on SO by this same title.
I had a table mapping and model that were working. Then the schema was changed (I do not have direct control of the DB) such that a new Primary Key was introduced and the old Primary Key became the Foreign Key to another table. I believe this is the heart of the problem as no other entities seem to have issues
Mapping
Here is the method that maps my entity (called from OnModelCreating)
private static void MapThing(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Thing>().ToTable("ThingTable");
modelBuilder.Entity<Thing>().HasKey(p => p.Id);
modelBuilder.Entity<Thing>().Property(p => p.Id).HasColumnName("NewId");
modelBuilder.Entity<Thing>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Thing>().Property(p => p.FileName).HasColumnName("ColumnWhosNameChanged");
modelBuilder.Entity<Thing>().HasRequired(p => p.MetaDataOnThing);
}
The old PK of the table is now defined as a property on the model and it is the same name as the column (the reason it is not defined in the mapping above).
Model
Here is the Model (I have applied names that I hope will make it more clear what has changed):
public class Thing
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
//This used to be the PK, its names (Property AND Column) have not changed
public int OldId { get; set; }
//The column name for FileName changed to something else
public string FileName { get; set; }
//Unchanged
public byte[] Document { get; set; }
public string ContentType { get; set; }
//Navigation Property
public ThingMetaData MetaDataOnThing { get; set; }
}
Integration test
I removed a lot of structure to, hopefully, make it clear..the test is pretty straight forward
[TestMethod]
public void ThenThingWillBePersisted()
{
var thing = new Thing()
{
OldId = metaDataObject.Id,
Document = new byte[] { 42 },
FileName = "foo.jpg",
ContentType = "image/jpeg"
};
context.Things.Add(thing);
context.SaveChanges();
}
This test produces the error "A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column:'NewId'" and the inner exception points to the NewId as being the issue. It does so on the SaveChanges() call.
Admittedly, I have a lot more experience with nHibernate than I do with Entity Framework but I am pretty sure my mappings and model are setup properly.
Has anyone seen this issue and how did you solve it?

Cant see changes in database after SaveChanges() called entity framework

I have a table Device with only one column UID nvarchar(128) and here is my entity:
[Table( Name="Device" )]
public class Device
{
[Key]
public string UID { get; set; }
}
When I trying to insert entity and commit changes to database all is ok. But in database there are no new rows added. If I try to repeat this operation with the same UID - I get en eror
Violation of PRIMARY KEY constraint 'PK_dbo.Devices'. Cannot insert duplicate key in object 'dbo.Devices'. The duplicate key value is ...
What's wrong?
EDIT:
Here is my context:
public class DeviceContext : BaseDbContext, IDbContext
{
public DbSet<Entity.Device> Device { get; set; }
public new IDbSet<T> Set<T>() where T : class
{
return base.Set<T>();
}
public int SaveChanges()
{
return base.SaveChanges();
}
public void Dispose()
{
base.Dispose();
}
}
Fails SaveChanges() method. BaseDbContext is only "connectionstring layer".
DeviceContext context = new DeviceContext();
context.Device.Add(new Device() { UID = id });
context.SaveChanges();
Error says that data is already saved. So I think you are looking at wrong database. Verify connection string which is used by your DbContext.
BTW you can see which connection is used by watching at context.Database.Connection.ConnectionString property in debugger.

EntityFramework: How to configure Cascade-Delete to nullify Foreign Keys

EntityFramework's documentation states that the following behavior is possible:
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship, and when the principal is
deleted the foreign key will be set to null.
(from http://msdn.microsoft.com/en-us/jj591620)
However, I cannot achieve such a behavior.
I have the following Entities defined with code-first:
public class TestMaster
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<TestChild> Children { get; set; }
}
public class TestChild
{
public int Id { get; set; }
public string Name { get; set; }
public virtual TestMaster Master { get; set; }
public int? MasterId { get; set; }
}
Here is the Fluent API mapping configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TestMaster>()
.HasMany(e => e.Children)
.WithOptional(p => p.Master).WillCascadeOnDelete(false);
modelBuilder.Entity<TestChild>()
.HasOptional(e => e.Master)
.WithMany(e => e.Children)
.HasForeignKey(e => e.MasterId).WillCascadeOnDelete(false);
}
Foreign Key is nullable, navigation property is mapped as Optional, so I expect the cascade delete to work as described as MSDN - i.e. to nullify MasterID's of all children and then delete the Master object.
But when I actually try to delete, I get the FK violation error:
using (var dbContext = new TestContext())
{
var master = dbContext.Set<TestMaster>().Find(1);
dbContext.Set<TestMaster>().Remove(master);
dbContext.SaveChanges();
}
On SaveChanges() it throws the following:
System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.UpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.SqlClient.SqlException : The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TestChilds_dbo.TestMasters_MasterId". The conflict occurred in database "SCM_Test", table "dbo.TestChilds", column 'MasterId'.
The statement has been terminated.
Am I doing something wrong or did I misunderstood what the MSDN says?
It works indeed as described but the article on MSDN misses to emphasize that it only works if the children are loaded into the context as well, not only the parent entity. So, instead of using Find (which only loads the parent) you must use eager loading with Include (or any other way to load the children into the context):
using (var dbContext = new TestContext())
{
var master = dbContext.Set<TestMaster>().Include(m => m.Children)
.SingleOrDefault(m => m.Id == 1);
dbContext.Set<TestMaster>().Remove(master);
dbContext.SaveChanges();
}
This will delete the master from the database, set all foreign keys in the Child entities to null and write UPDATE statements for the children to the database.
After following #Slauma's great answer I was still getting same error as OP.
So don't be as naive as me and think that the examples below will end up with same result.
dbCtx.Entry(principal).State = EntityState.Deleted;
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
// code above will give error and code below will work on dbCtx.SaveChanges()
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
dbCtx.Entry(principal).State = EntityState.Deleted;
First load the children into context before setting entity state to deleted (if you are doing it that way).