EF6 : DbUpdateConcurrencyException : "The database operation was expected to affect X row(s)" - c#

The minimal project sources to reproduce the issue is here :
https://wetransfer.com/downloads/8d9325ce7117bb362bf0d61fc7c8571a20220708100401/326add
===================
This error is a classic; In layman's terms it is usually caused by a "bad" insertion when a navigation is not properly taken in account, causing a faulty Ef state somewhere.
Many solutions have been posted along the years but I fail to see how my specific scenario could cause the issue!
My schema is a many-to-many between Groups and Users. The middle entity is named GroupUser.
There's a twist : Each GroupUser has an owned entity containing extra data, DataPayload. This choice was made for versatility -- we wanted that payload to be stored in its own table.
Schema:
public class User {
public Guid Id { get; private set; }
public IList<GroupUser> GroupUsers { get; private set; } = new List<GroupUser>();
public User(Guid id) { Id = id; }
}
public class Group {
public Guid Id { get; private set; }
public Group(Guid id) { Id = id; }
}
public class GroupUser {
public Guid Id { get; private set; }
public Guid GroupId { get; private set; }
public Guid UserId { get; private set; }
// Navigation link
public Group? Group { get; private set; }
public DataPayload? Data { get; private set; }
public GroupUser(Guid groupId, Guid userId, DataPayload data) {
Id = Guid.NewGuid(); //Auto generated
UserId = userId;
GroupId = groupId;
Data = data;
}
// This extra constructor is only there to make EF happy! We do not use it.
public GroupUser(Guid id, Guid groupId, Guid userId) {
Id = id;
UserId = userId;
GroupId = groupId;
}
}
public class DataPayload {
//Note how we did not defined an explicit Id; we let EF do it as part of the "Owned entity" mechanism.
///// <summary>foreign Key to the Owner</summary>
public Guid GroupUserId { get; private set; }
public int DataValue { get; private set; }
public DataPayload(int dataValue) {
DataValue = dataValue;
}
public void SetDataValue(int dataValue) {
DataValue = dataValue;
}
}
To make it all work, we configure the navigations like this :
// --------- Users ---------
builder
.ToTable("Users")
.HasKey(u => u.Id);
// --------- Groups ---------
builder
.ToTable("Groups")
.HasKey(g => g.Id);
// --------- GroupUsers ---------
builder
.ToTable("GroupUsers")
.HasKey(gu => gu.Id);
builder
.HasOne<User>() //No navigation needed
.WithMany(u => u.GroupUsers)
.HasForeignKey(gu => gu.UserId);
builder
.HasOne<Group>(gu => gu.Group) //Here, we did define a navigation
.WithMany()
.HasForeignKey(gu => gu.GroupId);
builder
.OwnsOne(gu => gu.Data,
navBuilder => {
navBuilder.ToTable("PayloadDatas");
navBuilder.Property<Guid>("Id"); //Note: Without this EF would try to use 'int'
navBuilder.HasKey("Id");
//Configure an explicit foreign key to the owner. It will make our life easier in our Unit Tests
navBuilder.WithOwner().HasForeignKey(d => d.GroupUserId);
}
);
//.OnDelete(DeleteBehavior.Cascade) // Not needed (default behaviour for an owned entity)
Now, you know how everything is defined.
Basic setup : works!
var group = new Group(groupId);
await dbContext.Groups.AddAsync(group);
await dbContext.SaveChangesAsync();
var user = new User(userId);
await dbContext.Users.AddAsync(user);
await dbContext.SaveChangesAsync();
Follow-up scenario : fails!
var groupUser = new GroupUser(groupId, userId, new DataPayload(dataValue: 777777));
user.GroupUsers.Add(groupUser);
await dbContext.SaveChangesAsync(); // Crash happens here!!!
Error:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException : The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded.
I suspect that EF gets confused by the addition of two entities at once, where it has to compute some Ids itself : the new GroupUser and the DataPayload it contains. I'm not sure how it's supposed to know that it needs to give an Id to the GroupUser first and then use that Id as the foreign key in PayloadData. But that's just me; it might or might not be related to the issue.
But what do I need to change?

The mistake was in GroupUser's id-less constructor:
Id = Guid.NewGuid();
The code needs to let EF manage the keys when it comes to owned entities such as DataPayload which rely on a foreign key (GroupUserId) that's still in the making at the time of saving.
If you set a key value (Guid.NewGuid()) yourself, then EF gets confused between:
linking the new DataPayload entity to the GroupUser entity where you've shoehorned an Id value,
OR
just expecting an empty value (foreign key) and setting all the keys (both the GroupUser's Id and DataPayload's GroupUserId) itself.
All in all, EF feels like you announced that you were about to let it create 1 entity, but you've pulled the rug under its feet and done it yourself, so it ends up with 0 entity to work with. Hence the error message.
It should have been :
Id = Guid.Empty;
With Guid.Empty, EF clearly identifies that this entity is new and that has to be the same one as the one you told it to create and link to the new PayloadData -- that is, the instance that you've set in GroupUser.Data.

Related

"Cannot insert duplicate key" when using Automapper to map to an existing Entity Framework entity

I am trying to update a database row by reading the entity (using Entity Framework 6), mapping new properties onto that entity (using AutoMapper) and then calling context.SaveChanges().
The problem I am having surrounds the navigational properties within my entity. It appears that during the mapping, a new object is being created and assigned to the navigational property, rather than the existing one's properties simply being updated.
Here's are my domain objects:
public class ParagraphComponent : IReportComponent
{
public Guid ComponentId { get; set; }
public float LineHeight { get; set; }
public ReportTextList TextItems { get; set; } = new ReportTextList();
}
public class ReportTextList : IList<ReportText>
{
private readonly IList<ReportText> _list = new List<ReportText>();
public int Count => _list.Count;
public bool IsReadOnly => _list.IsReadOnly;
// Overrides for IList...
public void Add(ReportText item)
{
_list.Add(item);
}
// ...Remove(), Insert() etc.
}
public class ReportText
{
public Guid Id { get; set; }
public string Content { get; set; } = "";
}
Entity Framework entities:
public partial class ParagraphComponentEntity
{
public System.Guid ComponentId { get; set; } // ComponentId (Primary key)
public double LineHeight { get; set; } // LineHeight
public virtual System.Collections.Generic.ICollection<ReportTextEntity> ReportTexts { get; set; }
}
public partial class ReportTextEntity
{
public System.Guid Id { get; set; } // Id (Primary key)
public string Content { get; set; } // Content
}
What I am doing: I am taking data for a ParagraphComponent from an API endpoint to perform an update. I load the existing component based on ParagraphComponent.Id and then I map the new properties onto the existing entity.
This works fine:
public async Task<bool> EditComponent(IReportComponent editedComponent)
{
var currentParagraphComponentEntity = await Context
.ParagraphComponents
.FirstOrDefaultAsync(x => x.ComponentId == editedComponent.ComponentId)
.ConfigureAwait(false);
Mapper.Map(editedComponent as ParagraphComponent, currentParagraphComponentEntity);
Context.SaveChanges();
}
I can see in debug that the properties are mapped correctly, but when SaveChanges() is called I get the following error:
Violation of PRIMARY KEY constraint 'PK_ReportText'. Cannot insert duplicate key in object 'dbo.ReportText'
It appears that the mapping process is assigning a new object to the ParagraphComponentEntity.ReportTexts property, thus Entity Framework sees it as an "Add" rather than an "Update", so it tries adding a new row to that table which errors because of the primary key enforcing the Id to be unique.
My AutoMapper configuration:
CreateMap<ParagraphComponent, ParagraphComponentEntity>()
.ForMember(dest => dest.LineHeight, src => src.MapFrom(s => s.LineHeight))
.ForMember(dest => dest.ReportTexts, src => src.MapFrom(s => s.TextItems))
.ForMember(dest => dest.ComponentId, src => src.MapFrom(s => s.ComponentId))
.ForAllOtherMembers(src => src.Ignore());
If AutoMapper is creating a new instance for the ReportTexts navigational property is the issue, how do I get around it?
It seems that your code which is fetching data from database is only fetching the main resource ParagraphComponent. Collection of ReportTexts by default will not be fetched into EF context because as far as I know EF is lazy loading - you need to eagerly load referenced entities with .Include(..) for instance.
If I am right - then before mapping your data into entity ReportTexts collection is empty and your Mapping code is really creating new items in that collection. These entities are comming from outside of EF context (read about EF ChangeTracking) so 'it' thinks that these are new entities which needs to be inserted into database. These objects obviously already contains Id set with existing keys - so that's where you get conflicts.
I think if you will eager load your entity, then EF should perform Update instead

Foreign key not populating in Entity Framework

I cannot get a table to update correctly that should be linking two of my entities. To explain in more detail...I have two entities, Class and Teacher, with a relationship in the form of:
Teacher can be assigned to many classes
Class can only have one teacher.
Below are these two entities.
public class Teacher
{
[Required, Key]
public Guid Id { get; private set; }
[StringLength(50)]
public string Name { get; set; }
public string Email { get; set; }
public List<Class> Classes = new List<Class>();
public Teacher()
{
Id = new Guid();
}
public Teacher(Guid id)
{
Id = id;
}
public void AssignClass(Class newClass)
{
Classes.Add(newClass);
}
}
public class Class
{
[Required, Key]
public Guid Id { get; private set; }
[Required, StringLength(20)]
public string Name { get; set; }
[Required, Range(5, 30)]
public int Capacity { get; set; }
public Teacher Teacher { get; set; }
public IEnumerable<StudentClass> StudentClasses { get; set; }
public Class()
{
Id = new Guid();
}
public Class(Guid id)
{
Id = id;
}
}
When I generate my migrations I get a foreign key of TeacherId in the Classes table as expected. Here is the SQL:
CREATE TABLE [dbo].[Classes] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR (20) NOT NULL,
[Capacity] INT NOT NULL,
[TeacherId] UNIQUEIDENTIFIER NULL,
CONSTRAINT [PK_Classes] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Classes_Teachers_TeacherId] FOREIGN KEY ([TeacherId]) REFERENCES [dbo].[Teachers] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_Classes_TeacherId]
ON [dbo].[Classes]([TeacherId] ASC);
My class derived of DBContext looks like:
public class SchoolDatabaseContext : DbContext
{
public DbSet<Student> Students { get; private set; }
public DbSet<Class> Classes { get; private set; }
public DbSet<Teacher> Teachers { get; private set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
public SchoolDatabaseContext(DbContextOptions<SchoolDatabaseContext> options) : base(options)
{
}
}
No configuration for those entities yet. I use DI to serve the DbContext to the controller and that all seems fine.
I have aimed for a DDD type structure, but to make this issue easier to debug I have stripped everything all the way back to the controller so it is basically... controller => DbContext.
Here is my code in the controller:
[HttpPost]
[Route("assign-teacher-to-class")]
public async Task<IActionResult> AssignClass([FromBody] AssignTeacherToClass assignTeacherToClass)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var teacher = await schoolDatabaseContext.Teachers.FindAsync(assignTeacherToClass.TeacherId);
var classToAssign = await schoolDatabaseContext.Classes.FindAsync(assignTeacherToClass.ClassId);
teacher.AssignClass(classToAssign);
schoolDatabaseContext.Entry(teacher).State = EntityState.Modified;
await schoolDatabaseContext.SaveChangesAsync();
return Ok(teacher);
}
When I debug through the ids are fine from the post body, they are assigned correctly to the DTO AssignClass and the calls to the DbContext to find the data for each type (teacher and class) are fine. I then call a method in my teacher type to add the class to the List Classes property (see teachers entity code at beginning for reference), I then Save the changes with the DbContext method and Problem Defined Here: at no stage does the TeacherId in the database update whilst debugging/completing. I have tried all I can think of like instantiating collections in different ways, changing collection types, looking for config that might help map these entities in this way, stripping out all extra layers, changing accessibility of properties and classes and few more.
Any help would really be appreciated as I am getting a bit defeated on this one and I feel like this relationship should be fairly straight forward. I actually was able to get my many to many working with a bridge class so I was surprised to get stuck on this one :(
Thanks
try this:
var teacher = await schoolDatabaseContext.Teachers.Include(x => x.Classes).SingleOrDefaultAsync(x => x.Id == assignTeacherToClass.TeacherId);
I don't think teacher.Classes gets tracked by DbContext otherwise.
There are multiple ways to accomplish this with EF Core. It is easiest to find if you call it what the docs call it "Related Data".
Here is the parent doc: Related Data
Specifically as #Y Stroli has illustrated the Eager Loading method.
The below example is shown on the eager loading reference to load multiple levels of related data:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.ToList();
}
As of EF Core 5.0 you can also do filtered includes:
using (var context = new BloggingContext())
{
var filteredBlogs = context.Blogs
.Include(blog => blog.Posts
.Where(post => post.BlogId == 1)
.OrderByDescending(post => post.Title)
.Take(5))
.ToList();
}
As the suggestion from lvan, you should change public List<Class> Classes = new List<Class>(); to public List<Class> Classes { get; set; } = new List<Class>();.
For your current code, it seems you want to add Class and return the teacher, if so, you need to include the exsiting classes to teacher like below, otherwise, it will only return the new adding class.
public async Task<IActionResult> AssignClass()
{
var assignTeacherToClass = new AssignTeacherToClass {
TeacherId = new Guid("52abe5e0-bcd4-4827-893a-26b24ca7b1c4"),
ClassId =new Guid("50354c76-c9e8-4fc3-a7c9-7644d47a6854")
};
var teacher = await _context.Teachers.Include(t => t.Classes).FirstOrDefaultAsync(t => t.Id == assignTeacherToClass.TeacherId);
var classToAssign = await _context.Classes.FindAsync(assignTeacherToClass.ClassId);
teacher.AssignClass(classToAssign);
_context.Entry(teacher).State = EntityState.Modified;
await _context.SaveChangesAsync();
return Ok(teacher);
}
One more note, you need to configure SerializerSettings.ReferenceLoopHandling like
services.AddMvc()
.AddJsonOptions(opt => {
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You need to define the connection between Teacher and Class.
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Class>()
.HasOne<Teacher>(p => p.Teacher)
.WithMany(q => q.Classes)
.HasForeignKey(r => r.TeacherId);
}
Also add TeacherId prop to Class.

Sequential ID per foreign key in EF Core

I am designing a database with Entity Framework Core which should contain two entity types:
An entity named "Channel" with a unique ChannelId
An entity named "Message" with a foreign key ChannelId and a MessageId
The MessageId must be unique for each channel, and it should be counted starting at 1.
My first try to implement this was to use a composite key for the Message entity with ChannelId and MessageId, but it does not have to stay this way. However I don't how to auto-generate the MessageId with EF Core.
So I tried to get the last MessageId for the current Channel incremented it and tried to insert:
public class DatabaseContext : DbContext
{
public void AddMessage(Message message)
{
long id = Messages
.Where(m => m.ChannelId == message.ChannelId)
.Select(m => m.MessageId)
.OrderByDescending(i => i)
.FirstOrDefault()
+ 1;
while (true)
{
try
{
message.MessageId = id;
Messages.Add(insert);
SaveChanges();
return;
}
catch
{
id++;
}
}
}
}
This code does not work. After an exception occurred EF Core does not insert the item with the incremented ID. In addition to that it seems to be very inefficient in situation with concurrent inserts.
Is there a more elegant solution to solve this problem when I use an additional ID in the messages table as primary key and maybe some additional tables?
Concept
After long research I found a solution for the problem:
I added a MessageIdCounter row to my Channels table.
Unlike classical code, SQL allows an atomic conditional write. This can be used for optimistic concurrency handling. First we read the counter value and increment it. Then we try to apply the changes:
UPDATE Channels SET MessageIdCounter = $incrementedValue
WHERE ChannelId = $channelId AND MessageIdCounter = $originalValue;
The database server will return the number of changes. If no changes have been made, the MessageIdCounter must have changed in the meantime. Then we have to run the operation again.
Implementation
Entities:
public class Channel
{
public long ChannelId { get; set; }
public long MessageIdCounter { get; set; }
public IEnumerable<Message> Messages { get; set; }
}
public class Message
{
public long MessageId { get; set; }
public byte[] Content { get; set; }
public long ChannelId { get; set; }
public Channel Channel { get; set; }
}
Database context:
public class DatabaseContext : DbContext
{
public DbSet<Channel> Channels { get; set; }
public DbSet<Message> Messages { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
var channel = builder.Entity<Channel>();
channel.HasKey(c => c.ChannelId);
channel.Property(c => c.MessageIdCounter).IsConcurrencyToken();
var message = builder.Entity<Message>();
message.HasKey(m => new { m.ChannelId, m.MessageId });
message.HasOne(m => m.Channel).WithMany(c => c.Messages).HasForeignKey(m => m.ChannelId);
}
}
Utility method:
/// <summary>
/// Call this method to retrieve a MessageId for inserting a Message.
/// </summary>
public long GetNextMessageId(long channelId)
{
using (DatabaseContext ctx = new DatabaseContext())
{
bool saved = false;
Channel channel = ctx.Channels.Single(c => c.ChannelId == channelId);
long messageId = ++channel.MessageIdCounter;
do
{
try
{
ctx.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
const string name = nameof(Channel.MessageIdCounter);
proposedValues[name] = messageId = (long)databaseValues[name] + 1;
entry.OriginalValues.SetValues(databaseValues);
}
} while (!saved);
return messageId;
}
}
For successfully using EF Core's concurrency tokens I had to set MySQL's transaction isolation at least to READ COMMITTED.
Summary
It is possible to implement an incremental id per foreign key with EF Core.
This solution is not perfect because it needs two transactions for one insert and is therefore slower than an auto-increment row. Furthermore it's possible that MessageIds are skipped when the application crashes while inserting a Message.

EF6 - error when insert entity with related entites only by navigation property

I need to insert an entity WITH a related entity inside, both in a single DbSet.Add invocation.
One-to-many between Course and CourseProfesor (CourseProfesor is the entity connecting Courses and Profesors)
Entities:
public class Course
{
public Course() { }
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
...
public virtual ICollection<CourseProfesor> Profesors { get; set; }
}
public class CourseProfesor
{
public CourseProfesor() { }
[Key]
public int ID { get; set; }
[Required, Index, Column(Order = 0)]
public int CourseID { get; set; }
[Required, Index, Column(Order = 1)]
public int ProfesorID { get; set; }
...
[ForeignKey("CourseID")]
public virtual Course Course { get; set; }
[ForeignKey("ProfesorID")]
public virtual Profesor Profesor { get; set; }
}
Mappings:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().HasMany(x => x.Profesors).WithRequired(x => x.Course);
modelBuilder.Entity<CourseProfesor>().HasRequired(x => x.Course).WithMany(x => x.Profesors);
modelBuilder.Entity<CourseProfesor>().HasRequired(x => x.Profesor).WithMany(x => x.Courses);
}
Controller:
public ActionResult Add(Course course, int profesorId = 0)
{
if (profesor > 0)
{
course.Profesors = new List<CourseProfesor>();
course.Profesors.Add(new CourseProfesor() { Course = course, CourseID = 0, ProfesorID = profesorId, From = DateTime.Now, Role = ... });
}
Facade.Create(course);
return Json(new {statusText = "Course Added"});
}
Facade.Create(entity) executes a CreateCommand which will in turn invoke
DbContext.Set(entity.GetType()).Add(entity)
The exception I get:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property value(s) of 'Course.ID' on one end of a relationship do not match the property value(s) of 'CourseProfesor.CourseID' on the other end
how to assign CourseProfesor.CourseID if I don't know the ID of the course yet, since both are new entities?
As you can see in the controller code, I used to worked that out that by setting only the navigation property and EF would auto-populate foreign key accordingly.
This is important: This was working fine on EF5, I got that error after updating to EF6
Any clues why EF6 throws that exception while EF5 didn't? and how to solve it without having to first create the Course and then the CourseProfesor relationship entity?
A couple of things stand-out:
course.Profesors.Add(new CourseProfesor() { Course = course, CourseID = 0, ProfesorID = profesorId, From = DateTime.Now, Role = ... });
When using navigation properties I steer away from defining the FKs fields in the entities, but if you need to define them, you should avoid setting them. Use the navigation properties only. Setting FKs can be misleading because if you were to pass this Course out with it's CourseProfessors and consume it, there would be a ProfessorID set, but no Professor reference available.
Regarding your specific problem the probable issues would be the CourseID = 0 set above, and the two-way mapping between the Course and CourseProfessor.
modelBuilder.Entity<Course>().HasMany(x => x.Profesors).WithRequired(x => x.Course);
//modelBuilder.Entity<CourseProfesor>().HasRequired(x => x.Course).WithMany(x => x.Profesors);
modelBuilder.Entity<CourseProfesor>().HasRequired(x => x.Profesor).WithMany(x => x.Courses);
Try removing that redundant mapping.
Also the definition of the linking table has an ID as a PK, but isn't set up with a generation option. The two FKs are set up Order=0 and Order=1 as well, which looks odd without considering the PK?
Beyond that, I can't say I like that facade pattern. To eliminate the possibility, what happens if you go to the DB Context Courses DBSet?
context.Courses.Add(course);
context.SaveChanges();

Does EF track related models, and set their foreign keys when needed?

I have two EF models/classes that have relation between them: Member and MembershipSeason. One Member can have several MembershipSeasons. One MembershipSeason model has a foreign key reference (MemberID) to Member model in db.
Member.cs
public class Member
{
public int MemberID { get; set; }
//some properties left out
public virtual ICollection<MembershipSeason> MembershipSeasons { get; set; }
}
MembershipSeason.cs
public class MembershipSeason
{
[Key]
public int MembershipSeasonID { get; set; }
//some properties left out
public int MemberID { get; set; }
public virtual Member Member { get; set; }
}
I experimented to post those two models to the same Create method together in the same time. I discovered that EF tracks those two models and saves them into db as new models. It also links those two models by setting MemberID of the new Member model as foreign key to the new MembershipSeason model in db. I guess this is planned behaviour? – I mean the fact EF sets foreign key to the related models automatically seems to be expected behaviour – how things should work. Therefore I guess I don’t need to save Member model first, obtain it’s MemberID and use it for MembershipSeason and save it separately in the Create method? (because EF does the work for you)
db.Members.Add(member);
db.MembershipSeasons.Add(membershipSeason);
await db.SaveChangesAsync();
The above and the below Create method works in the way that no MemberID property is needed to be set directly to MembershipSeason model, because EF does it automatically.
MemberController.cs
public class MemberController : Controller
{
private MembersContext db = new MembersContext();
//some code left out
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "MemberNumber,FirstName,LastName")] Member member,
[Bind(Include = "HasPaidMembership,SeasonId")] MembershipSeason membershipSeason)
{
try
{
if (ModelState.IsValid)
{
db.Members.Add(member);
db.MembershipSeasons.Add(membershipSeason);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
catch (DataException /* dex */)
{
}
return View(member);
}
}
I am quite new with EF and ASP.NET MVC, so I am trying to figure these things out. Any help appreciated – thanks.
It also links those two models by setting MemberID of the new Member model as foreign key to the new MembershipSeason model in db. I guess this is planned behaviour?
TL;DR: Yes
Yes, it has to be the required behavior. Lets start with reads:
public class Organization
{
public Guid Id { get; set; }
}
public class Employee
{
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public Organization Organization { get; set; }
}
public Employee GetEmployeeWithOrganization(guid id)
{
var result = _context.Employees
.Include(e => e.Organization)
.FirstOrDefault(e => e.Id = id);
}
Basically when you ask EF to include the navigation property you'd get an object graph kinda like:
Employee
- Id : <guid1>
- OrganizationId : <guid2>
- Organization : object
- Id : <guid2>
It would be common sense to assume that because EF should keep track of entities because what happens if you do this:
var employee = GetEmployeeWithOrganization(<guid1>)
var org = new Organization { id = guid.NewGuid() }; //<guid3>
employee.Organization = org;
_context.SaveChanges();
Which one of these is a valid object:
A:
Employee
- Id : <guid1>
- OrganizationId : <guid2> // <-- difference
- Organization : object
- Id : <guid3>
B:
Employee
- Id : <guid1>
- OrganizationId : <guid3> // <-- difference
- Organization : object
- Id : <guid3>
A isn't valid, because you can't rely on the values and programming against that object would not only be a completely nightmare, but at the database level doesn't make sense. B is valid, it is data you can rely on.
This also means you can precache items and EF will write them up automagically. Consider:
var org = GetOrganization(<guid3>);
var emp = GetEmployee(<guid1>);
Assert.That(emp.Organization, Is.Not.Null); // passes
This happens because EF is tracking org and because EF is configured with org as a FK to employee.

Categories