Microsoft.EntityFrameworkCore.DbUpdateException - asp.net-core

I have a comment table where it have a two foreign key(i.e Userid and postid). I was trying to insert data into comment table using these two foreign key but was unable to insert. This is my post table
public class Post
{
public int Id { get; set; }
public string PostText { get; set; }
public string Title { get; set; }
public bool Status { get; set; }
public DateTime PostDate { get; set; }
public virtual List<Comment> Comments { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
And this is my comment table
public class Comment
{
public int Id { get; set; }
public string CommentText { get; set; }
public DateTime CommentTime { get; set; }
public bool Status { get; set; }
public ApplicationUser CommentBy { get; set; }
public Post Posts { get; set; }
}
Comment service
public void Save(Comment comment)
{
_context.Set<Comment>().Add(comment);
_context.SaveChanges();
}
And this is my controller
[HttpPost]
public ObjectResult SaveComment([FromBody] Comment comment)
{
if (ModelState.IsValid)
{
try
{
_commentService.Save(comment);
return Ok("comment saved");
} catch (Exception e)
{
return BadRequest(e);
}
} else
{
return BadRequest("Model is not valid");
}
}
And the error is
{Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Violation of PRIMARY KEY constraint 'PK_AspNetUsers'. Cannot insert duplicate key in object 'dbo.AspNetUsers'. The duplicate key value is (966fc417-8757-4bac-89b2-9975d4f2cd41).
Cannot insert explicit value for identity column in table 'Posts' when IDENTITY_INSERT is set to OFF.
The statement has been terminated.
this is my api request

The reason is that you will also insert a new comment.CommentBy when you're inserting a brand new comment:
// the `comment` here is constructed by model binding, which is a brand new entity
[HttpPost]
public ObjectResult SaveComment([FromBody] Comment comment)
{
// ...
_commentService.Save(comment); // save the brand new comment
// ...
}
"A brand new comment" means this Comment entity and its related propreties are all untracked. When saving a brand new entity, EF Core will also create related entities for you automatically.
To avoid this behavior, you could mark the state of comment.CommentBy as Unchanged,
_context.Entry(comment.CommentBy).State= EntityState.Unchanged;
so that the EF Core will not create a new CommentBy (i.e. an ApplicationUser) for you. But be careful: you must make sure the ApplicationUser already exists.
The same goes Post.
Another approach is much safer. As suggested by #Khai Nguyen in the comment, you should get ApplicationUser and Post instance from database, so that the EF Core knows there's already a ApplicationUser and a Post within database and won't insert new ApplicationUser or Post for you.

Related

ASP MVC Entity Framework core. Update many to many relationship on form post

I'd like to know a good practice to update a many to many relationship when submit form.
I got these two entities and I use the default many to many relationship from EF core 5:
public class BlogEntry
{
public int Id { get; set; }
[Required]
[MaxLength(200)]
public string Title { get; set; }
[Required]
public string EntryText { get; set; }
[NotMapped]
public IEnumerable<string> CategoriesToPublish { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<BlogEntry> BlogEntries { get; set; }
}
context:
public DbSet<BlogEntry> BlogEntries { get; set; }
public DbSet<Category> Categories { get; set; }
And I have a form witha multiselect field to represent this relationship. See Image
form
I'm not using the relation property on the form(maube I should, but I don't know), I have another property to convert the relationship into a list of strings called CategoriesToPublish so I can load the multiselect and retrieve the selection on post.
On the post action method, I want to iterate the this CategoriesToPublish and update all the relationships.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Publish(BlogEntry blogEntry)
{
if (ModelState.IsValid)
{
blogEntry.Categories = await _context.Categories.Where(x => x.BlogEntries.Any(x => x.Id == blogEntry.Id)).ToListAsync();
await UpdateCategories(blogEntry);
_context.Update(blogEntry);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(List));
}
return View(blogEntry);
}
But the problem that I'm facing is that the Categories relationship is not loaded on postback. And if I try to load it manually and save context, I get an error saying SqlException: Violation of PRIMARY KEY constraint 'PK_BlogEntryCategory'. Cannot insert duplicate key in object 'dbo.BlogEntryCategory'
I am not sure how to approach this problem. Any advice?
After lot of searching I found out a solution.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Publish(BlogEntry blogEntry)
{
if (ModelState.IsValid)
{
blogEntry = _context.Update(blogEntry).Entity;
blogEntry.Categories = await _context.Entry(blogEntry).Collection(u => u.Categories).Query().ToListAsync();
await UpdateCategories(blogEntry);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(List));
}
return View(blogEntry);
}
I retrieve the blogEntry after Update:
blogEntry = _context.Update(blogEntry).Entity;
At this point the Categories are still empty, but now we can load them from DB again:
blogEntry.Categories = await _context.Entry(blogEntry).Collection(u => u.Categories).Query().ToListAsync();
and boila, now we are good to iterate all the Categories change them if needed, it doesn't complain about duplicate keys.

How to retrieve the objects participating in a many-to-many relationship?

I have a many-to-many relationship between User(Contributor) and TaskType. I want to assign to a variable only TaskTypes that have the current user among their contributors. Obviously, I can somehow do this using the functionality of the Entity Framework. But how? I use asp.net core 3.
Below I try unsuccessfully to do it:
public IQueryable<TaskType> ContributedTaskTypes
{
get
{
// This code doesn't work!
return _dbContext.TaskTypes.Where(t => t.Contributors.Contains(c => c.UserId == CurrentUserId));
}
}
Below are definitions of all models involved in this relationship:
public class TaskType
{
public int Id { get; set; }
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public virtual List<Contribution> Contributors { get; set; }
}
public class Contribution
{
public int Id { get; set; }
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int TaskTypeId { get; set; }
public TaskType TaskType { get; set; }
}
public class ApplicationUser : IdentityUser
{
public virtual List<Contribution> ContributedToTaskTypes { get; set; }
}
For those queries it is always easiest to do queries where you can dot to the result.
Here is the query with sql-like syntax
from row in _dbContext.Contribution
where row.UserId == CurrentUserId
select row.TaskType
By selecting row.TaskType instead of just row you get it correct entity.
Is that Contributors property retrieved correctly from DB? if it is not you must call Include() method to load/refer relational referenced entities
_dbContext.TaskTypes.Include(p=>p.Contributors).Where(..
more: https://learn.microsoft.com/en-us/ef/core/querying/related-data
In Addition, if EF Core Table Relation is not correctly defined, you should follow
this instruction: https://www.entityframeworktutorial.net/efcore/configure-many-to-many-relationship-in-ef-core.aspx

Error with EF core savecontext using Identity class

I have a quiz sql schema and I am also using ASP.NET Identity. When I attempt to insert an answer from the user into the UserAnswer table I get the error below. It seems like it is trying to insert into the User table but I don't want that?
Violation of PRIMARY KEY constraint 'PK_AspNetUsers'. Cannot insert
duplicate key in object 'dbo.AspNetUsers'. The duplicate key value is
(71ddfebf-18ba-4214-a01e-42ca0f239804). Cannot insert explicit value
for identity column in table 'Questions' when IDENTITY_INSERT is set
to OFF. The statement has been terminated.
foreach (ProfileViewModel pvm in profileViewModels)
{
UserAnswer ua = new UserAnswer();
ua.QuestionId.ID = pvm.Question.ID;
ua.ApplicationUser.Id = userId;
ua.AnswerText = pvm.Answer;
_userAnswerRepository.Create(ua);
}
which just does
protected void Save() => _context.SaveChanges();
and the model is
public class UserAnswer
{
public UserAnswer()
{
this.QuestionId = new Question();
this.ApplicationUser = new ApplicationUser();
}
public int Id { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public Question QuestionId { get; set; }
public string AnswerText { get; set; }
}
I guess I need to use virtual and not the actual object for some reason.. The model looked fine but it seems to confused the update
public class UserAnswer
{
public UserAnswer()
{
this.Question = new Question();
this.User = new ApplicationUser();
}
public int Id { get; set; }
public string UserId { get; set; } // FK to ApplicationUser
public int QuestionId { get; set; } // FK to Question
public string AnswerText { get; set; }
public virtual Question Question { get; set; }
public virtual ApplicationUser User { get; set; }
}

ASP.Net MVC 4 SimpleMembership UserID as Foreign Key

Dear SO members please help me to get unstuck in what I thought should be an easy task but I have been stuck in for 2 days. I have a couple of tables that need to have a FK to the UserId of the currently logged in user. Here is an example
UserProfile
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string ManagerName { get; set; }
public string PhoneNumber { get; set; }
}
A customer entity
public class Customer
{
[Key]
public int CustomerId { get; set; }
public string Names { get; set; }
[ForeignKey("UserProfile")]
public int UserId { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
The DB Context
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Customer> Customers { get; set; }
}
Customer Controller
[HttpPost]
[InitializeSimpleMembership]
[ValidateAntiForgeryToken]
public ActionResult Create(Customer model)
{
if (ModelState.IsValid)
{
model.UserProfile = db.UserProfiles.FirstOrDefault(u => u.UserId == WebSecurity.CurrentUserId);
db.Customers.Add(model);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UserId = new SelectList(db.UserProfiles, "UserId", "UserName", model.UserId);
return View(model);
}
Also tried
[HttpPost]
[InitializeSimpleMembership]
[ValidateAntiForgeryToken]
public ActionResult Create(Customer model)
{
if (ModelState.IsValid)
{
model.UserId = WebSecurity.GetUserId(User.Identity.Name);
db.Customers.Add(model);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UserId = new SelectList(db.UserProfiles, "UserId", "UserName", model.UserId);
return View(model);
}
In each case I get the error message
"The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Customers_dbo.UserProfile_UserId". The conflict occurred in database "ValueCardProject", table "dbo.UserProfile", column 'UserId'.
The statement has been terminated."
All I want to do is to be able to create tables with a FK to the UserID of the logged in user. For the interest of full disclosure, Customers should not be created directly as users, in my use case the current logged in users is a Retailer, so what I am trying to accomplish is to create a customer record with the FK to RetailerId (which is the UserId in the UserProfile).
Thanks for your time.
I believe your foreign key attribute on UserId is in the wrong place.
you should be doing this instead:
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual UserProfile UserProfile { get; set; }

CodeFirst - Update single property

We are using EF5, Code First approach to an MVC4 app that we're building. We are trying to update 1 property on an entity but keep getting errors. Here's what the class looks like which the context created:
public partial class Room
{
public Room()
{
this.Address = new HashSet<Address>();
}
public int RoomID { get; set; }
public Nullable<int> AddressID { get; set; }
public Nullable<int> ProductVersionID { get; set; }
public string PhoneNumber { get; set; }
public string AltPhone { get; set; }
public string RoomName { get; set; }
public string Description { get; set; }
public string Comments { get; set; }
public string Notes { get; set; }
public virtual ICollection<Address> Address { get; set; }
}
Here's our ViewModel for the view:
public class RoomDetailsViewModel
{
//public int RoomID { get; set; }
public string RoomName { get; set; }
public string PhoneNumber { get; set; }
public string AltPhone { get; set; }
public string Notes { get; set; }
public string StateCode { get; set; }
public string CountryName { get; set; }
public string ProductVersion { get; set; }
public int PVersionID { get; set; }
public List<SelectListItem> ProductVersions { get; set; }
public Room Room { get; set; }
}
Here's the Controller Action being called on "Save":
[HttpPost]
public virtual ActionResult UpdateRoom(RoomDetailsViewModel model)
{
var db = new DBContext();
bool b = ModelState.IsValid;
var rooms = db.Rooms;
var rm = rooms.Where(r => r.RoomID == model.Room.RoomID).Single();
//List<Address> address = db.Addresses.Where(a => a.AddressID == rm.AddressID).ToList<Address>();
rm.ProductVersionID = model.PVersionID;
//rm.Address = address;
db.Entry(rm).Property(r => r.ProductVersionID).IsModified = true;
//db.Entry(rm).State = System.Data.EntityState.Modified;
db.SaveChanges();
return View("RoomSaved", model);
}
All this view does is display data and allow the user to change the Product Version (from a SelectList), so, in the Room Entity, all we are updating is the ProductVersionID property, nothing else. We can get the data to display properly but when we click "save", we get this error:
An object of type 'System.Collections.Generic.List`1[[Models.Address,
Web.Mobile.TestSite, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]]' cannot be set or removed from the Value
property of an EntityReference of type 'Models.Address'.
As you can see by the Controller Action, we've tried several different things but all seem to produce this error. I've tried to populate the model.Room.Address collection with an Address, without, but still get this error.
I read this StackOverflow article and this article as well but neither have solved my problem.
ANY help with this would be greatly appreciated!
After hours and hours of digging, turns out that EF did not import some of the PK's for my DB tables. What tipped me off to this was on the Room class, the PK RoomID did not have the [Key] attribute on it. I tried to reimport the table through the edmx but it never came through as a key (even though it's clearly marked PK in the DB). So, to get around it, I created a partial class of my DBContext and override the OnModelCreating event and included the key, like so:
public partial class DBContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Models.Room>().HasEntitySetName("Rooms");
modelBuilder.Entity<Models.Room>().HasKey(r => r.RoomID);
}
}
Once this was done, the Action saved the record as hoped.
I hope this helps someone else!