User is duplicated when calling Reply - c#

I'm trying to make a simple forum using MVC and I can't figure out why the user that is posting the reply is getting duplicated.
Here is the Reply Action:
[HttpPost]
public ActionResult Reply(string Title, string Content,int ReplyTo)
{
Post masterPost = db.Posts.FirstOrDefault(p => p.PostID == ReplyTo);
Post post = new Post();
post.PostID = 0;
post.CreatedOn = DateTime.Now;
post.ModifiedOn = DateTime.Now;
post.ReplyTo = masterPost;
post.Forum = db.Forums.FirstOrDefault(f=>f.ForumID == masterPost.Forum.ForumID);
post.User = (User)Session["User"];
post.Title = Title;
post.Content = Content;
//if (ModelState.IsValid)
//{
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("View", "Posts", new { id = ReplyTo });
//}
return View(post);
}
This is the Post entity:
public class Post
{
[Key]
public int PostID { get; set; }
public string Title { get; set; }
[DataType(DataType.MultilineText)]
public string Content { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public virtual Forum Forum { get; set; }
public virtual User User { get; set; }
public virtual Post ReplyTo { get; set; }
}
This is the User entity:
public class User
{
public int UserID { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public DateTime RegisteredOn { get; set; }
}
Whenever the ReplyTo action is called it creates the Post but it also duplicates the User that is stored in the session (with a different UserID).
What am I doing wrong?

Even though you don't state it, it looks like you use Entity Framework.
If so, the culprit is probably this line
post.User = (User)Session["User"];
The "User" you store in session is now disconnected from Entity Framework, so EF assumes it is a brand-new user.
There are several ways to solve this. The one I prefer is to also add a UserId property to your Post class and use that
public class Post
{
// Stuff
public virtual int UserId { get; set; }
}
Then do:
post.UserId = ((User)Session["User"]).Id;
Entity Framework uses a convention to understand that you want to link that user to that post.

So when you say "it also duplicates the User", I conclude what you mean is that you get a duplicate User type in the DB after the db.SaveChanges.
Given that as my presumption, then I would speculate that the User stored in Session["Usser"] does not have an existing UserID property set, AND/OR your EF model does not have the Post to User multiplicity association set. Ordinarily, I would expect to see UserId property in Post, and your sample above does not show this.
Check your model to make sure you have the assocation set between User and Post, and that there's a foreign key property linking Post to User. The Post object requires a foreign key referencing UserId.
Take a look at Step 5 in this example from Microsoft.

Related

EF Core Join Table for One To Many relationship

I have a Database Schema like this.
I am working on gettingDetailByPostID API which needs to return 1 Object as follows.
Guid PostId;
Guid UserId;
String? Username;
String? MediaPath;
int Likes; // count the number of likes of the post
String? Caption;
bool IsLiked;
IEnumerable<Comment>? Comments; // List of comments in the post
I am having difficulty in joining the tables together to get more data from the User and Comment tables. How can I do this with ef core 6.
firsr have a DbSet property for in your db context inherited class which may look like this public DbSet<Post> Posts { get; set; }.
then inject the dbcontext to your required service as constructor injection.
the you can do var post = yourDbContenxt.Posts.FirstOrDefaultAsync(p=>p.PostId==yourPostId);
The best way to do this would be in a single transaction.
I believe you already have configured EF with navigation and you should have an entities like this
public class Post
{
public int post_id { get; set; }
public int user_id { get; set; }
public string caption { get; set; }
public string? media_path { get; set; }
virtual public List<Like> Likes { get; set; }
}
public class Like
{
public int post_id { get; set; }
public int user_id { get; set; }
virtual public Post Post { get; set; }
virtual public UserProfile UserProfile { get; set; }
}
public class UserProfile
{
public int post_id { get; set; }
public int user_id { get; set; }
public string username { get; set; }
virtual public Post Post { get; set; }
virtual public List<Like> Likes { get; set; }
}
If not, you should have a look on the microsoft documentation or on this great tutorial site
If it is configured with navigation (but without lazy loading to avoid infinite loop, and to have an ecological use of your server ressources :) )
Then you can build your object within the same context instanciation such as this short exemple
public class DetailedPost
{
Guid PostId;
Guid UserId;
String? Username;
String? MediaPath;
int Likes; // count the number of likes of the post
String? Caption;
bool IsLiked;
IEnumerable<Comment>? Comments; // List of comments in the post
public DetailedPost GettingDetailByPostID(int id)
{
DbContext context = new DbContext();
UserProfile userprofile = context.UserProfile
.Include(user => user.Posts.Where(p => p.post_id == id)) // Select post by id
.ThenInclude(post => post.Comments) // Include the Comment Db relative to post selected
.Include(user => user.Likes); // Include like db relative to userProfile db
// Return object construction
return new DetailedPost()
{
PostId = userprofile.post_id,
UserId = userprofile.user_id,
Username = userprofile.username,
Caption = userprofile.Posts.FirstOrDefault().Caption,
Likes = userprofiles.Likes.Count(),
};
}
}
I hope it helps !

Why I get this kind of error The UPDATE statement conflicted with the FOREIGN KEY constraint

I am trying to update one column from my table and when I press Update buttton I get error
The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_Tickets_AspNetUsers_UserId". The conflict occurred in database "CarPlatzz", table "dbo.AspNetUsers", column 'Id'. The statement has been terminated.
In my Model I have something like
public class Ticket
{
[Key]
public int Id { get; set; }
[Display(Name = "Opis")]
public string Description { get; set; }
[Display(Name = "Datum i vrijeme slanja")]
public string DateAndTime { get; set; } = DateTime.Now.ToString("dd/MM/yyyy HH:mm");
[Display(Name = "Datum i vrijeme zavrsetka")]
public DateTime Answered { get; set; }
[Display(Name = "Vrsta tiketa")]
public int TicketTypeID { get; set; }
[ForeignKey("TicketTypeID")]
public virtual TicketType TicketType { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser ApplicationUser { get; set; }
public int? ClientId { get; set; }
[ForeignKey("ClientId")]
public virtual Client Client { get; set; }
public string Status { get; set; } = TicketStatus.Otvoren.ToString();
}
And Here is my Action in Controller
public IActionResult Upsert(TicketVM ticketVM)
{
var userName = User.FindFirstValue(ClaimTypes.Email);
var user = HttpContext.User.Identity.Name;
if (ModelState.IsValid)
{
if (ticketVM.Ticket.Id == 0)
{
ticketVM.Ticket.ApplicationUser = _db.ApplicationUsers.FirstOrDefault(u => u.Email == userName);
ticketVM.Ticket.Status = TicketStatus.Otvoren.ToString();
_unitOfwork.Ticket.Add(ticketVM.Ticket);
}
else
{
ticketVM.Ticket.Status = ((TicketStatus)Convert.ToInt32(ticketVM.Ticket.Status)).ToString();
_unitOfwork.Ticket.Update(ticketVM.Ticket);
}
_unitOfwork.Save();
return RedirectToAction(nameof(Index));
}
return View(ticketVM);
}
I check a couple of post here but nothing helped me.
So I change
public int UserId {get;set;}
to
public int? UserId {get;set;}
But problem here is that after update column UserId in NULL which is not solution for me.
So the problem is in my Upsert method in ELSE block
else
{
ticketVM.Ticket.Status = ((TicketStatus)Convert.ToInt32(ticketVM.Ticket.Status)).ToString();
_unitOfwork.Ticket.Update(ticketVM.Ticket);
}
ApplicationUser.cs
public class ApplicationUser : IdentityUser<int>
{
[Required]
[Display(Name = "Ime")]
public string Name { get; set; }
[Required]
[Display(Name = "Adresa")]
public string StreetAddress { get; set; }
[Required]
[Display(Name = "Grad")]
public string City { get; set; }
[Required]
[Display(Name = "Postanski broj")]
public string PostalCode { get; set; }
public int? ClientId { get; set; }
[ForeignKey("ClientId")]
public Client Client { get; set; }
[NotMapped]
public string Role { get; set; }
[NotMapped]
public IEnumerable<SelectListItem> RoleList { get; set; }
[NotMapped]
public IEnumerable<SelectListItem> ClientList { get; set; }
}
REFERENCE is here
LINK 1
LINK 2
LINK 3
LINK 4
I can see a couple serious issues with your approach. The primary culprit that sets off alarms is this:
ticketVM.Ticket.ApplicationUser = _db.ApplicationUsers.FirstOrDefault(u => u.Email == userName);
followed by:
_unitOfwork.Ticket.Add(ticketVM.Ticket);
Why are you using a _unitOfWork, but then going to some _db reference which looks like a direct DbContext. A Unit of Work wraps a DbContext instance so all entities should be resolved from that single DbContext. I would expect to see something like:
ticketVM.Ticket.ApplicationUser = _unitOfWork.ApplicationUsers.Single(u => u.Email == userName);
If the ApplicationUser is being pulled from a different DbContext instance than the Ticket is being persisted to, that second DbContext won't be tracking the instance and would result in potentially trying to insert a duplicate User.
Based on the insert or update nature of the method, you would want the setting of the ApplicationUser reference to only occur on the Insert scenario so that the user reference doesn't change on an update unless that is what is supposed to happen.
The second warning flag is the use of FirstOrDefault. This method really needs a restriction in Linq to NOT function without an OrderBy like the Skip and Take methods. If you expect a record back it is better to use Single which will throw an exception if it doesn't find an expected matching row. With FirstOrDefault you may get a null reference if the e-mail matching doesn't line up and that would attempt to remove a user reference from the ticket.
The next recommendation would be to avoid mixing entities and view models. Entities represent data state. View models represent view state. Serializing entities into a view model means sending more data than the view actually needs, and can lead to performance issues when a serializer starts "touching" navigation properties tripping lazy loads. Keep view models as POCOs and project the entity values needed into the view model properties. Serializing an Identity User and potentially it's associated details is almost certainly something you don't want to be sending over the wire to a web client.
Statements like:
_unitOfwork.Ticket.Update(ticketVM.Ticket);
are a high risk for data tampering within a system. While your UI may only present some expected fields that a user might modify, the ENTIRE entity is open to modification when using this approach. When updating data, load the entity from data state and copy across the validated details from the view model then save the entity.
The error message is self-explanatory. It seems that your application tried to update a row in the table and the value did not exist in the referenced table, which broke the referential integrity and caused the error.

EF Optional Relationships / Nullable / Navigation Table

I solved this, answer below.
I am new to EF and having a lot of difficulty trying to get an optional relationship. I am looking to have a relationship where I have ApiLogItem Model with an UserId property which can be null / anonymous user or a logged in user to track all Api calls. The goal is to have Existing Users who do some create a new object to be linked to that object. I do not want to create new Users every time a new ApiLogItem is created.
I have tried a dozen variations with virtual / foreign key attributes and I am stumped. It works great for null / anonymous user but once I attach an actual user to the ApiLogItem it will not insert. I get this error:
{"Violation of PRIMARY KEY constraint 'PK_AspNetUsers'. Cannot insert
duplicate key in object 'dbo.AspNetUsers'. The duplicate key value is
(09c0d2e2-b003-4be8-a62a-08d7268af58e).\r\nThe statement has been
terminated."}
I have tried following this tutorial but alas no luck.
https://www.learnentityframeworkcore.com/conventions/one-to-many-relationship#targetText=EF%20Core%20will%20create%20a,public%20class%20Author
public class ApiLogItem
{
[Key]
public long Id { get; set; }
[Required]
public int StatusCode { get; set; }
[Required]
public string Method { get; set; }
[MaxLength(45)]
public string IPAddress { get; set; }
public Guid? UserId { get; set; }
public ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser<Guid>
{
[MaxLength(64)]
public string FirstName { get; set; }
[MaxLength(64)]
public string LastName { get; set; }
public List<ApiLogItem> ApiLogItems { get; set; }
}
Error happens when I want to create a new ApiLogItem:
using (ApplicationDbContext _dbContext = new ApplicationDbContext(_optionsBuilder.Options))
{
_dbContext.ApiLogs.Add(apiLogItem);
await _dbContext.SaveChangesAsync();
}
I have reviewed several other stackoverflow issues and none seem to fix. You can find the repository here:
https://github.com/enkodellc/blazorboilerplate
You are calling applicationDbContextSeed.SeedDb(); in your Startup class each time you run, and in your SeedDb method, you are adding a user with a static id 09C0D2E2-B003-4BE8-A62A-08D7268AF58E.
The first time you run, it will create that user; the second time, it will fail because that user (with that id) already exists.
I figured it out. It needs a virtual in the parent and just the id in the child. I need to learn more about EF as it is not intuitive to me. Will post a better answer later today after testing.
public class ApplicationUser : IdentityUser<Guid>
{
[MaxLength(64)]
public string FirstName { get; set; }
[MaxLength(64)]
public string LastName { get; set; }
public ICollection<ApiLogItem> ApiLogItems { get; set; }
}
public class ApiLogItem
{
[Key]
public long Id { get; set; }
[Required]
public int StatusCode { get; set; }
[Required]
public string Method { get; set; }
[MaxLength(45)]
public string IPAddress { get; set; }
public Guid? UserId { get; set; }
}

Entity Framework Core domain and classes

I'm currently following a project being developed by csharpfritz (of microsoft). It's called, "corewiki". Some form of "wikipedia" like project.
Here's the link to the repository on github: CoreWiki by Jeff Fritz
On the domain class for comments he writes this:
I'm trying to understand why he created an entity FromDomain class and ToDomain class:
// Main model
public class CommentDAO
{
public int Id { get; set; }
public int ArticleId { get; set; }
public virtual ArticleDAO Article { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
[NotMapped]
public Instant Submitted { get; set; }
public string Content { get; set; }
}
public static CommentDAO FromDomain(Core.Domain.Comment comment)
{
return new CommentDAO
{
AuthorId = comment.AuthorId,
Content = comment.Content,
DisplayName = comment.DisplayName,
Email = comment.Email,
Id = comment.Id,
ArticleId = comment.ArticleId,
Submitted = comment.Submitted
};
}
public Core.Domain.Comment ToDomain()
{
return new Core.Domain.Comment
{
AuthorId = AuthorId,
Content = Content,
DisplayName = DisplayName,
Email = Email,
Id = Id,
ArticleId = this.Article.Id,
Submitted = Submitted
};
}
That's simply mapping code to map a domain model to a data access object and vice versa. You can implement this in many ways, such as the author showed, or using explicit conversion operators, or using a tool like AutoMapper.
See for example Having the domain model separated from the persistence model (first Google hit for "why separate domain model from dao") for an explanation of why you'd want that.

Updating FK relationships in Entity Framework 4.1

I think I have read every article and stack overflow question regarding this, but cannot work out the solution. Let me start out with my models
public class Entry
{
public Entry ()
{
DateEntered = DateTime.Now;
}
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
public string Number { get; set; }
public string FbId { get; set; }
[ReadOnly(true)]
public DateTime DateEntered { get; set; }
public string AccessToken { get; set; }
//Relationsips
public Backgrounds Background { get; set; }
public Cars Car { get; set; }
}
public class Backgrounds
{
public int Id { get; set; }
public string Name { get; set; }
public string Filename { get; set; }
}
public class Cars
{
public int Id { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
}
Now in my controller, I am updating the entry. Like follows
// PUT /api/entries/5
public HttpResponseMessage Put(Entry entry)
{
if(ModelState.IsValid)
{
_db.Entries.Attach(entry);
_db.Entry(entry).State = EntityState.Modified;
_db.SaveChanges();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
My Entry model gets updated correctly, but if for eg entry.Background.Name changes, this will not be persisted to the database. My controller is accepting the entire entry model including its relationships => Backgrounds and Cars. However any value that is changed to the relationship is not updated or reflected. Any elegant solution without having to query the database then updating? I dont want to have any extra queries or lookups before I update.
Thanks
Tyrone
You must manually tell EF about all changes done to the object graph. You told EF just about change to entry instance but you didn't tell it about any change to related entities or relations itself. There is no elegant way to solve this. You have generally two options:
You will use some DTOs instead your entities and these DTOs will have some flag like IsDirty - when you receive object graph back to your controller you will reconstruct entities from DTOs and set their state based on IsDirty. This solution needs further extensions for example if your client can also delete relations.
You will query object graph from database and merge your incoming changes to entities retrieved from database.
There are some partial solutions like forcing to save changes to all related objects by setting their state to modified and identifying new objects by Id == 0 but again these solutions work only in specific scenarios.
More complex discussion about this problem.

Categories