Duplicate key violation occuring even with EntityState set - c#

I am trying to update the values of a project item, specifically the assigned users to the project. The feature is to be able to remove users from an already created project via an edit button. In the function below I am finding the relevant project using a passed in projectID and then finding all users associated to the project. From there I am finding the specific users from the group of users associated to the project which I am looking to remove. Finally the user is removed using the List .Remove() method. (The company value is also found companyFind and assigned to its relevant value)
The AssignedUsersToProject will now only contain users who should be associated to the project once .Remove() is changed. I have looped through and set them to have an Unchanged state, the same goes for the company.
However when saving in the database to update the record the following error is shown despite the use of Unchanged
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_ApplicationUserProject'. Cannot insert duplicate key in object 'dbo.ApplicationUserProject'. The duplicate key value is (5685a830-cb82-4b60-b459-c0852cc74563, 229698cd-eebb-432b-e6eb-08d9b62cc799).
The statement has been terminated.
How could I prevent this from occuring? I thought unchanged would stop the record from being re-inserted into the database on update. Would appreciate any insight into this. Thanks in advance
Project Model
public class Project
{
[Key]
public Guid ProjectId { get; set; }
public Guid companyID { get; set; }
[Required]
public string ProjectName { get; set; }
public ICollection<ApplicationUser> AssignedUsersToProject { get; set; }
public Company assignedCompanyForProject { get; set; }
}
ApplicationUser model
public class ApplicationUser : IdentityUser
{
[JsonIgnore]
public ICollection<Project> Projects { get; set; }
}
Controller function
[HttpPut("removeUser/{username}/{projectID}")]
public async Task<IActionResult> RemoveUserFromProject(string username, Guid projectID)
{
var project = await _context.Projects.FindAsync(projectID);
var query = _context.Users.Where(u => u.Projects.Any(p => p.ProjectId.ToString().Equals(project.ProjectId.ToString()))); //find all users for the project
var companyFind = _context.Companies.First(c => c.CompanyId.ToString().Equals(project.companyID.ToString())); //companyID to current projects company ID
//_context.Attach(project);
project.AssignedUsersToProject = query.ToList();
project.assignedCompanyForProject = companyFind;
var removeUser = query.First(u => u.UserName.Equals(username));
project.AssignedUsersToProject.Remove(removeUser);
foreach (var x in project.AssignedUsersToProject)
{
_context.Entry(x).State = EntityState.Unchanged;
}
_context.Entry(project.AssignedUsersToProject).State = EntityState.Unchanged;
_context.Entry(project.assignedCompanyForProject).State = EntityState.Unchanged;
await _context.SaveChangesAsync();
return NoContent();
}
Edit
Suggested changes
[HttpPut("removeUser/{username}/{projectID}")]
public async Task<IActionResult> RemoveUserFromProject(string username, Guid projectID)
{
var project = _context.Projects.Include(u => u.AssignedUsersToProject).FirstOrDefault(p => p.ProjectId.ToString().Equals(projectID.ToString()));
// remove user where username is match
project.AssignedUsersToProject.Remove(project.AssignedUsersToProject.First(u => u.UserName == username));
}

Related

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

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.

Entity code first duplicate entries (MVC Identity)

Until now I have always worked with my own DAL for SQL Server.
In a new project I decided to work with Entity in a MVC project and Identity.
I use to work with bridge tables.
Here is my IdentityModels (simplified)
ApplicationUser
public class ApplicationUser : IdentityUser
{
[Required]
public string Surname { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
Group
public class Group
{
[Key]
public int Id { get; set; }
[Display(Name = "Nom du Groupe")]
[Required]
[CustomRemoteValidation("IsGroupNameExist", "Groups", AdditionalFields =
"Id")]
public string Name { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUsers { get; set;
}
And DbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public DbSet<Group> Groups { get; set; }
}
All tables I need are created and seems well created (ApplicationUser Group and ApplicationUserGroups).
The trouble is :
I have 3 groups (A, B, C) with Id 1,2,3. I"m adding a user in table ApplicationUser with 3 groups in the Groups property.
First part is OK, it adds the good values in the bridge table (ApplicationUsersGroup) BUT It adds groups A, B, C again, with Id 4,5,6 in Group table.
The CreateAsync method of UserManageris not the point (It's the same with just an Add).
If I have a look in the debugger, I can see that when I pass to the add method the user object, in the Groupsproperty, I have a ApplicationUsers property with inside the Groups property. For me, it could be the reason, but if I remove the Groups property from ApplicationUser, code first doesn't create the ApplicationUserGroups.
I'm wrong with something, but what? How can I have a user without an additional entry in Grouptable?
Thank you for your help.
UPDATE
Ok, now I understood why duplicates are added, but in my case, how to avoid that?
Here is the involved part of the Register method:
List<Group> selectedItems = new List<Group>();
foreach (GroupTableViewModel item in model.SelectedGroups)
{
if (item.Selected == true) selectedItems.Add(new Group { Id = item.Id, Name = item.GroupName });
}
var user = new ApplicationUser { Name = model.Name, Surname = model.Surname, UserName = model.Surname + "." + model.Name, Email = model.Email,Groups=selectedItems};
string password = RandomPassword.Generate(8, 8);
var result = await UserManager.CreateAsync(user, password);
CreateAsync() is the identity method. I don't understand how it adds the user (I don't see any Add() or 'SaveChanges() inside with JustDecompile).
Maybe I'm wrong again but if I want to attach an entity to the context I have to create a new context, which will be different from the context used by the CreateAsync() method.
So help needed...
This is a common issue that people unfamiliar with EF face. Because of the disconnected state of entities in the object context, EF will attempt to insert the entities in the relationships, even though they already exist. In order to solve, you need to tell EF that the entities are not new by setting their state to Unchanged. Take a look at this article from Julie Lerman and the related SO question/answer.
https://msdn.microsoft.com/en-us/magazine/dn166926.aspx
Entityframework duplicating when calling savechanges

Can't get an entity property

There is a problem with my Db which I figured only now, when I started to work at the web api. My USER entity:
public class User { get; set; }
{
public int UserId { get; set; }
public string Name { get; set; }
}
And this is ACTIVITY
public class Activity
{
public int ActivityId { get; set; }
public User User { get; set; }
}
I added an activity and checked in SSMS. Everything seems to be good, there is a field named UserId which stores the id. My problem is when I try to get a User from an Activity because I keep getting null objects. I didn't set anything special in my DbContext for this.
This is where I'm trying to get an User from an Activity object:
public ActionResult ActivityAuthor(int activityId)
{
Activity activityItem = unitOfWork.Activity.Get(activityId);
return Json(unitOfWork.User.Get(activityItem.User.UserId));
}
Relation between User and Activity
The User property of Activity class should be marked as virtual. It enables entity framework to make a proxy around the property and loads it more efficiently.
Somewhere in your code you should have a similar loading method as following :
using (var context = new MyDbContext())
{
var activity = context.Activities
.Where(a => a.ActivityId == id)
.FirstOrDefault<Activity>();
context.Entry(activity).Reference(a => a.User).Load(); // loads User
}
This should load the User object and you won't have it null in your code.
Check this link for more information msdn
my psychic debugging powers are telling me that you're querying the Activity table without Include-ing the User
using System.Data.Entity;
...
var activities = context.Activities
.Include(x => x.User)
.ToList();
Alternatively, you don't need Include if you select properties of User as part of your query
var vms = context.Activities
.Select(x => new ActivityVM() {UserName = x.User.Name})
.ToList();

Assign UserId to my child entity results in a 2n UserId1 column in the sql table

I am using
asp.net core
asp.net core identity
entity framework core
My User entity has a child navigation property 'Projects' because a User can have many Projects and a Project entity has one User navigation property:
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public User User { get; set; }
}
When I update my database with the migrations then in the Project sql table a column UserId is created which is also the foreign key to User.Id.
That happend all automatically by convention.
But when I want to save a new project with the related userId
context.Projects.Add(project);
project.User.Id = userId; // User is null here
await context.SaveChangesAsync();
I just get an exception. I can also write:
project.User = new User{ Id = userId };
await context.SaveChangesAsync();
How can store that project entity with just that userId without retrieving the whole user again?
P.S. When I create an additional UserId property in the project entity then another UserId column is created in the sql table with the name 'UserId1'.
UPDATE
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public User User { get; set; }
}
modelBuilder.Entity<Project>().HasOne(x => x.User).WithMany(p => p.Projects).IsRequired();
You're almost there. If you use your code in this order:
context.Projects.Add(project);
project.User = new User{ Id = userId };
await context.SaveChangesAsync();
... you only have to add one more line:
context.Projects.Add(project);
project.User = new User{ Id = userId };
context.Attach(project.User);
await context.SaveChangesAsync();
Now Entity Framework knows that the user is an existing user and it will use its ID as foreign key for project. In this case, project.User is a so-called stub entitiy: an incomplete entity object that's used to avoid a roundtrip to the database.
As an alternative, you can add a property UserId to Project, which will be picked up by EF as foreign key belonging to Project.User.

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