Updating related entity in EF Core - entity-framework-core

I am using Entity Framework Core with a code-first approach. I hit this error when saving the changes to an entity which has a change to one of its related objects:
"The property 'BirdId' on entity type 'Bird' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal."
I hit the error when I save the changes to an entity of type Observation with a updated to the related Bird object. I have included the models setup below:
public class Observation
{
[Key]
public int ObservationId { get; set; }
// other properties...
public int BirdId { get; set; }
public Bird Bird { get; set; }
}
The Bird class looks like this:
public class Bird
{
[Key]
public int BirdId { get; set; }
// other properties...
public ICollection<Observation> Observations { get; set; }
}
I have relied solely on the EF Core model conventions (I've not added code in the OnModelCreating method) which, from the EF migration, sets up the database like so:
migrationBuilder.CreateTable(
name: "Observation",
columns: table => new
{
ObservationId = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BirdId = table.Column<int>(nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_Observation", x => x.ObservationId);
table.ForeignKey(
name: "FK_Observation_Bird_BirdId",
column: x => x.BirdId,
principalTable: "Bird",
principalColumn: "BirdId",
onDelete: ReferentialAction.Cascade);
});
Have I setup the model correctly?
My code for saving the updated Observation looks like this:
observation.BirdId = model.Bird.BirdId;
var bird = await _birdRepository.GetBirdAsync(model.Bird.BirdId);
observation.Bird = bird;
observation.LastUpdateDate = _systemClock.GetNow;
await _unitOfWork.CompleteAsync();
However, I think the problem lies in the way the relationship is setup in the models. Can anyone shed any light on the problem?

observation.Bird is linked to observation.BirdId by convention. You're not supposed to change both and it is generally recommended to only change the navigation property.
I'm going to assume that model is a view model and so you can't use its Bird directly, but basically you can leave out the first line of code and simply set observation.Bird.
EF gets confused in this case because you are trying to change the relation twice.

Related

"The association has been severed but the relationship is either marked as 'Required' or is implicitly required..."

I am getting the following error when trying to add a migration:
PS C:\Code\morpher.ru\Morpher.Database> dotnet ef migrations add AddQazaqFeatures --startup-project=../Morpher.Database.Design
Build started...
Build succeeded.
System.InvalidOperationException: The association between entity types 'Service' and 'Deployment' has been severed but the relationship is either m
arked as 'Required' or is implicitly required because the foreign key is not nullable. If the dependent/child entity should be deleted when a requi
red relationship is severed, then setup the relationship to use cascade deletes. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLoggin
g' to see the key values.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.HandleConceptualNulls(Boolean sensitiveLoggingEnabled, Boolean forc
e, Boolean isCascadeDelete)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.CascadeDelete(InternalEntityEntry entry, Boolean force, IEnumerable`1 fore
ignKeys)
...
My code:
public class Deployment
{
public int Id { get; set; }
public virtual Service Service { get; set; }
public int ServiceId { get; set; }
public string Host { get; set; }
public short? Port { get; set; }
public string BasePath { get; set; }
}
public class Service
{
public int Id { get; set; }
public string Name { get; set; }
public string UrlSlug { get; set; }
public virtual ICollection<Endpoint> Endpoints { get; set; }
public virtual ICollection<Deployment> Deployments { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Service>().HasData(new Service
{
Name = "Веб-сервис «Морфер»",
UrlSlug = "ws",
Id = 1
});
modelBuilder.Entity<Deployment>().HasData(new Deployment
{
Host = "ws3.morpher.ru",
ServiceId = 1,
Id = 1
});
modelBuilder.Entity<Deployment>(entity =>
{
entity.Property(e => e.Host).IsRequired().HasMaxLength(256);
entity.Property(e => e.BasePath).HasMaxLength(512);
entity.HasOne(deployment => deployment.Service)
.WithMany(service => service.Deployments)
.HasForeignKey(d => d.ServiceId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_Deployments_Services");
});
}
There are numerous StackOverflow questions mentioning the same error (1, 2, 3), but they are mostly to do with removing entities while not having a CASCADE delete policy or a nullable foreign key.
In my case, I am trying to add new rows and I don't see why it is considering the relationship 'severed'. Is setting ServiceId = 1 not enough?
I was able to reproduce the issue in latest at this time EF Core 3.1 version (3.1.28) by first removing the model data seeding code (HasData calls), then adding migration for just creating the tables/relationships, then adding the data seeding code and attempting to add new migration.
It does not happen in latest EF Core 6.0, so apparently you are hitting EF Core 3.1 defect/bug which has been fixed somewhere down on the road. So you either need to upgrade to a newer EF Core version (with all associated burdens like retesting everything, breaking changes etc.), or use the workaround below.
The workaround is to replace the DeleteBehavior.Restrict with either ClientNoAction or NoAction. Values of that enum and documentation of what they do is kind of messy, but all these 3 values seem to generate one and the same regular enforced FK constraint (with no cascade) in the database, and differ only by client side behavior, or in other words, what does EF Core change tracker do with related tracked entities when "deleting" a principal entity. And in this particular case, `Restrict" throws exception when there are tracked (loaded) related entity instances, while the other two won't.
I know you think you are just "adding data", but EF Core model data seeding is more than that - it tries to keep that data, so in some circumstances it needs to update or delete previously added data. Which in general works, except when there are bugs in the EF Core codebase, like in this case.

Deleting association between one or zero to one entities with EntityFramework

I have entities set up something like this:
public class MyThing
{
public int Id { get; set; }
public virtual MyOtherThing { get;set; }
}
public class MyOtherThing
{
public int Id { get; set; }
public virtual MyThing MyThing { get; set; }
}
My intention is that 'MyThing' can have one or none of MyOtherThing, and I also want a navigation link from MyOtherThing to it's parent.
I have configured the following EntityBaseConfiguration for the 'MyOtherThing' entity:
this.HasOptional(x => x.MyThing)
.WithOptionalPrincipal(x => x.MyOtherThing);
I can assign and modify MyOtherThing to MyThing no problem, but when I want to unassign 'MyOtherThing' from 'MyThing', how do I do this?
I tried the following:
myThing.MyOtherThing = null;
and then editing the entity by setting the EntityState.Modified state, but this didn't remove the association between the entities.
I tried adding the following to my MyThing entity, but this resulted in an EF 'Multiplicity is not valid' error when updating my database model:
public int? MyOtherThingId{ get; set; }
Thanks in advance!
I tried the following:
myThing.MyOtherThing = null;
If you want to remove an optional dependent entity (here: MyOtherThing) from a principal entity (here MyThing) by setting it to null, you have to pull the entity from the database with the dependent entity included, for example:
var mything = context.MyThings.Include(m => m.MyOtherThing)
.Single(t => t.Id == idValue);
(It's also OK when the belonging MyOtherThing is loaded into the context later, for example by lazy loading).
Without Include, myThing.MyOtherThing already is null and EF doesn't detect any change. Note that the statement myThing.MyOtherThing = null; doesn't execute lazy loading, which is a bit confusing because with collections the behavior is different.
By the way, the dependent entity can also be removed from the database directly, which is more efficient.
var ot = context.Set<MyOtherThing>().Find(idValue);
context.Set<MyOtherThing>().Remove(ot);
context.SaveChanges();

EF many-to-many relationship and data duplication

I have a trouble with EF (6.1.3)
I have created next classes (with many-to-many relationship):
public class Record
{
[Key]
public int RecordId { get; set; }
[Required]
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Record> Records{ get; set; }
}
And method:
void AddTags()
{
Record[] records;
Tag[] tags;
using (var context = new AppDbContext())
{
records = context.Records.ToArray();
}//remove line to fix
tags = Enumerable.Range(0, 5).Select(x => new Tag()
{
Name = string.Format("Tag_{0}", x),
Records= records.Skip(x * 5).Take(5).ToArray()
}).ToArray();
using (var context = new AppDbContext()){ //remove line to fix
context.Tags.AddRange(tags);
context.SaveChanges();
}
}
If I use two contexts, the records (which were added to created tags) will be duplicated. If I remove marked rows - problem disappears.
Is there any way to fix this problem without using the same context?
If you can, better reload entities or not detach them at all. Using multiple context instances in application is overall making things much more complicated.
The problem for you comes from the Entity Framework entity change tracker. When you load entitites from your DbContext and dispose that context, entities get detached from entity change tracker, and Entity Framework has no knowledge of any changes made to it.
After you reference detached entity by an attached entity, it (detached entity) immediately gets into entity change tracker, and it has no idea that this entity was loaded before. To give Entity Framework an idea that this detached entity comes from the database, you have to reattach it:
foreach (var record in records) {
dbContext.Entry(record).State = EntityState.Unchanged;
}
This way you will be able to use records to reference in other objects, but if you have any changes made to these records, then all these changes will go away. To make changes apply to database you have to change state to Added:
dbContext.Entry(record).State = EntityState.Modified;
Entity Framework uses your mappings to determine row in database to apply changes to, specifically using your Primary Key settings.
A couple examples:
public class Bird
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
public class Tree
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class BirdOnATree
{
[Column(Order = 0), Key, ForeignKey("Bird")]
public int BirdId { get; set; }
public Bird Bird { get; set; }
[Column(Order = 1), Key, ForeignKey("Tree")]
public int TreeId { get; set; }
public Tree Tree { get; set; }
public DateTime SittingSessionStartedAt { get; set; }
}
Here's a small entity structure so that you could see how it works. You can see that Bird and Tree have simple Key - Id. BirdOnATree is a many-to-many table for Bird-Tree pair with additional column SittingSessionStartedAt.
Here's the code for multiple contexts:
Bird bird;
using (var context = new TestDbContext())
{
bird = context.Birds.First();
}
using (var context = new TestDbContext())
{
var tree = context.Trees.First();
var newBirdOnAtree = context.BirdsOnTrees.Create();
newBirdOnAtree.Bird = bird;
newBirdOnAtree.Tree = tree;
newBirdOnAtree.SittingSessionStartedAt = DateTime.UtcNow;
context.BirdsOnTrees.Add(newBirdOnAtree);
context.SaveChanges();
}
In this case, bird was detached from the DB and not attached again. Entity Framework will account this entity as a new entity, which never existed in DB, even though Id property is set to point to existing row to database. To change this you just add this line to second DbContext right in the beginning:
context.Entry(bird).State = EntityState.Unchanged;
If this code is executed, it will not create new Bird entity in DB, but use existing instead.
Second example: instead of getting bird from the database, we create it by ourselves:
bird = new Bird
{
Id = 1,
Name = "Nightingale",
Color = "Gray"
}; // these data are different in DB
When executed, this code will also not create another bird entity, will make a reference to bird with Id = 1 in BirdOnATree table, and will not update bird entity with Id = 1. In fact you can put any data here, just use correct Id.
If we change our code here to make this detached entity update existing row in DB:
context.Entry(bird).State = EntityState.Modified;
This way, correct data will be inserted to table BirdOnATree, but also row with Id = 1 will be updated in table Bird to fit the data you provided in the application.
You can check this article about object state tracking:
https://msdn.microsoft.com/en-US/library/dd456848(v=vs.100).aspx
Overall, if you can avoid this, don't use object state tracking and related code. It might come to unwanted changes that are hard to find source for - fields are updated for entity when you don't expect them to, or are not updated when you expect it.

EF 6 Code-First TPH Inheritance Results in Migrations with Duplicate Columns (sometimes in triplicate)

I have an MVC5 application using EF 6.1
I am using table-per-hierarchy (TPH) inheritance for several entities and in every case I am incurring the same problem...that the migration generates duplicate column definitions within a single table even though the column is only declared in ONE of the derived classes.
CreateTable(
"dbo.ContactInfos",
c => new
{
ContactInfoId = c.Guid(nullable: false),
sContactInfoSubTypeId = c.Guid(nullable: false),
Value = c.String(nullable: false, maxLength: 150),
IsDefault = c.Boolean(nullable: false),
Deleted = c.Boolean(nullable: false),
BuildingId = c.Guid(), <-- Contained in ContactInfoBuilding
CompanyId = c.Guid(), <-- Contained in ContactInfoCompany
OccupancyId = c.Guid(), <-- Contained in ContactInfoOccupancy
PersonId = c.Guid(), <-- Contained in ContactInfoPerson
BuildingId1 = c.Guid(), <-- Why is this duplicated??
CompanyId1 = c.Guid(), <-- Why is this duplicated??
OccupancyId1 = c.Guid(), <-- Why is this duplicated??
Discriminator = c.String(nullable: false, maxLength: 128),
Occupant_OccupantId = c.Guid(), <-- Why is this triplicated??
})
My Base Class:
public abstract class ContactInfo
{
[Column("ContactInfoId")]
[Key]
public Guid ContactInfoId { get; set; }
[Column("sContactInfoSubTypeId")]
public Guid sContactInfoSubTypeId { get; set; }
[Column("Value")]
[MaxLength(150, ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "MaxLengthExceeded")]
public string Value{ get; set; }
[Column("IsDefault")]
public Boolean IsDefault { get; set; }
public virtual sContactInfoSubType sContactInfoSubType { get; set; }
}
My derived class:
public class ContactInfoOccupancy: ContactInfo
{
[Column("OccupancyId")]
public Guid OccupancyId { get; set; }
[ForeignKey("OccupancyId")]
public virtual Occupant Occupant { get; set; }
}
My DBContext Mapping Code:
modelBuilder.Entity<ContactInfo>().HasRequired(t => t.sContactInfoSubType).WithMany(t => t.ContactInfos).HasForeignKey(d => d.sContactInfoSubTypeId).WillCascadeOnDelete(false);
modelBuilder.Entity<ContactInfo>().Property(t => t.ContactInfoId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<ContactInfoOccupancy>().HasRequired(t => t.Occupant).WithMany(t => t.ContactInfoOccupancies).HasForeignKey(d => d.OccupancyId).WillCascadeOnDelete(false);
Finally, in my Occupant object, I have the ContactInfoOccupancies declared in the constructor and as a navigation property:
public partial class Occupant
{
public Occupant()
{
this.ContactInfoOccupancies = new List<ContactInfoOccupancy>();
}
...More stuff here that is unrelated...
public virtual ICollection<ContactInfoOccupancy> ContactInfoOccupancies { get; set; }
So, in summary: I declared my base class as abstract. I have the FK (OccupancyId for example) declared in only one derived class, and I have the mapping explicitly stated. I also have only the base-class declared as a DBSet in the DBContext. However, EF Code-First Migrations seems to be trying to add the fields twice or even three times (once as it should, a second time with the number one appended and a third time with an underscore between the table-name and the column-name). Any ideas how to stop this? Commenting out the erroneous lines from the migration doesn't work because the "snapshot" still thinks they exist and any insert/update attempts then fail because the "phantom columns" are still trying to be populated by calls to Context.SaveChanges
I also blew away my database and started a new migration from scratch, resulting in the above-listed migration...so this isn't an artifact resulting from an existing database having an inheritance schema pushed onto it.
Thanks in advance for any help...it will be greatly appreciated.
OK, the problem was that we were using ViewModels with MVC and the Scaffolder made the ViewModels inherit from the abstract, base-class Model of ContactInfo instead of the ContactInfoViewModel. We heavily modified the basic scaffolder to do this, so this was really our code that had a bug, not Microsoft's.
Since ViewModels are supposed to be ignorant of the database, we didn't have any mapping entries in the DBContext for the ViewModels, but the Migration-generator was seeing the relationships based on naming conventions (fields with the table-name followed by Id) within the ViewModels and artificially creating additional relationships within the DBContext due to the VMs inheriting from the base-class Model which was in the Context.
Changing the VMs to inherit from the ContactInfoViewModel removed these "implicit relationships" and all is good.

"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?