Trying to learn MVC and im a bit stuck on foreign keys. Trying to make a simple facebook style posting system.
Users, Posts, And Comments.
There can be many Users, Each User Can Have Many Posts To Their Wall by all other users, each post can have many comments.
The Posts To Comments relationship works fine (i presume due to the naming convention). However the users to posts relationship does not seem to work, when trying to add a user i get the error.
The INSERT statement conflicted with the FOREIGN KEY constraint MVC
What am i doing wrong here, could someone point me in the right direction please. (Also i know my DB structure sucks, im just messing about trying to learn MVC here).
USER
namespace Conference.Models
{
public class User
{
public Int32 UserID { get; set; }
public string Username { get; set; }
[ForeignKey("PostedToID")]
public virtual List<Post> Posts { get; set; }
}
}
POST
public class Post
{
[Key]
public Int32 PostID { get; set; }
//to get the name of the user who posted the post
public Int32 UserID { get; set; }
//to get the wall the post was posted to
public Int32 PostedToID { get; set; }
public DateTime PostDateTime { get; set; }
[Required(ErrorMessage = "{0} Is Required")]
[Display(Name = "Post Content")]
public String PostContent { get; set; }
public virtual List<Comment> Comments { get; set; }
}
}
COMMENT
public class Comment
{
public Int32 CommentID { get; set; }
//to get the id of the user that posted the comment
public Int32 UserID { get; set; }
public Int32 PostID { get; set; }
public DateTime CommentDateTime { get; set; }
[Required(ErrorMessage = "{0} Is Required")]
[Display(Name = "Comment Content")]
public String CommentContent { get; set; }
}
You can always set the FK relation explicitly. Remove ForeignKey attribute for Posts in User class.
// Foreign key to User
[ForeignKey("User")]
public Int32 PostedToID { get; set; }
Read this.
Related
I am currently working on a simple comments system.
I have a table that should store comments by following model:
public class Comments
{
[Key]
public int Id { get; set; }
[DataType(DataType.MultilineText)]
public string Text { get; set; }
public DateTime DateCreated { get; set; }
public int FromUserId { get; set; }
public int ToUserId { get; set; }
}
Comments are being written from one user to another on his UserPage.
I get userId from db, looking for id that corresponds my User.Identity.Name (name of authorized user) and pass it to FromUserId.
The question is How can I track on whoose page user is writing comment, in order to pass this to ToUserId.
UserPage is being created dynamically from db.
This is a bit of a long winded question. I've been going through a lot trying to learn the ins and outs of EF6 and MVC5 at the same time, so I apologize if anything I'm saying is not making sense or is plain old wrong. (please let me know if it is!)
The problem is arising when I'm trying to scaffold CRUD pages for any models that have foreign keys to the pre-made AspNetUsers table. (This and a few other tables are already part of the DB if you choose a new MVC5 project with authentication.)
I've been able to successfully scaffold fully working CRUD pages for all my models that don't include links to the Users table. I'm wondering if it's something I've misunderstood, or if something I've done is messing this up.
I've been reading a lot of answers to questions here on SA for problems that are similar to mine, but to no avail. It seems to me like such a simple thing should be easier, but I've been struggling with this for days now.
To try to isolate the problem, I've started a new MVC project with authentication and done nothing other than add my models file, migrate the DB to add the tables and try to scaffold the pages. The scaffolding itself completes successfully, but adds this line to the IdentityModels.cs file:
public System.Data.Entity.DbSet<TestWhatever.Models.ApplicationUser> IdentityUsers { get; set; }
which is not correct (learned that from another SA thread). There should only be user generated dbsets in this file.
Upon running the app, I get the following error:
Multiple object sets per type are not supported. The object sets 'IdentityUsers' and 'Users' can both contain instances of type 'TestWhatever.Models.ApplicationUser'.
Edit: It was suggested below that I simply remove the generated IdentityUsers line - however doing that causes compiler errors in the generated CRUD pages. Something is not going right here, I'm starting to think that EntityFramework doesn't know how to use its own UserManager to display and update Users. ::
Is what I'm doing along the right path? Am I not supposed to be using the inbuilt tables for user auth? If not, why are they there? Any insight is appreciated, I've been finding this all very confusing as any documentation or answered questions I find are never covering quite the same topic. Thanks.
The tables that are giving me problems look like this:
public class ExamInProgress
{
[Key]
public int ProgressId { get; set; }
[Required]
[Display(Name = "User")]
public string UserId { get; set; }
[ForeignKey("UserId")]
public ApplicationUser User { get; set; }
[Required]
[Display(Name = "Exam")]
public int ExamId { get; set; }
public virtual Exam Exam { get; set; }
}
public class CompletedExam
{
[Key]
public int CompletedExamId { get; set; }
[Required]
[Display(Name = "Date Completed")]
public DateTime DateCompleted { get; set; }
[Required]
[Display(Name = "Final Score")]
public decimal FinalScore { get; set; }
[Required]
[Display(Name = "Exam Name")]
public string ExamName { get; set; }
[Required]
[Display(Name = "User")]
public string UserId { get; set; }
[ForeignKey("UserId")]
public ApplicationUser User { get; set; }
public virtual Exam Exam { get; set; }
}
Another example of the tables I'm using: (there are more but mainly just ints and strings)
public class Exam
{
[Key]
public int ExamId { get; set; }
[Required]
[StringLength(100, ErrorMessage = "Exam name cannot be longer than 100 characters.")]
[Display(Name = "Exam Name")]
public string ExamName { get; set; }
[Display(Name = "Exam Description")]
public string ExamDescription { get; set; }
public virtual ICollection<Module> Modules { get; set; }
}
public class Question
{
[Key]
public int QuestionId { get; set; }
[Required]
[Display(Name = "Question Text")]
public string QuestionText { get; set; }
[Display(Name = "Question Order Index")]
[Range(0, int.MaxValue, ErrorMessage = "Index can not be negative")]
public int? QuestionOrderIndex { get; set; }
[Required]
[Display(Name = "Question Type")]
public int QuestionTypeId { get; set; }
[Required]
[Display(Name = "Module")]
public int ModuleId { get; set; }
public virtual QuestionType QuestionType { get; set; }
public virtual Module Module { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
public class QuestionType
{
[Key]
public int QuestionTypeId { get; set; }
[Required]
[StringLength(60, ErrorMessage = "QuestionType Name cannot be longer than 60 characters.")]
[Display(Name = "QuestionType Name")]
public string QuestionTypeName { get; set; }
[Display(Name = "QuestionType Description")]
public string QuestionTypeDescription { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
The error you're getting is due to having added the IdentityUsers property to your context. Your context is inheriting from IdentityDbContext<ApplicationUser>, and that class already has the following property:
public IDbSet<TUser> Users { get; set; }
Where TUser is the type passed in the generic, ApplicationUser. Adding your own DbSet for ApplicationUser creates two methods of access, and you get that error. Remove your property and you should be good.
I have been working on a project and I'm trying to get the cascade delete to kick in. I have a model below I use for comments. These comments can have replies that come off of them that call the comment class. What I'm trying to do is to make it delete all the replies that can flow off the comment.
Comment -> Reply -> Reply -> Reply -> so on.
If I'm going about this in the wrong direction, please let me know. I have tried to research into this but all I come up with is One-to-One and One-Many cascade codes. I'm using CodeFirst with MVC 4 to build my project.
Edited
public class Comment
{
// Properties
public long Id { get; set; }
[Required]
[StringLength(250, ErrorMessage = "{0} must be between {1} and {2} characters", MinimumLength = 2)]
public string Body { get; set; }
[Required]
public DateTime CreateDate { get; set; }
[Required]
[InverseProperty("Comments")]
public User Author { get; set; }
[InverseProperty("CommentCount")]
public Blog Blog { get; set; }
public bool Hidden { get; set; }
public long RepliesId { get; set; }
[InverseProperty("Replies")]
public virtual Comment Comments { get; set; }
[InverseProperty("Comments")]
public virtual ICollection<Comment> Replies { get; set; }
public virtual ICollection<Vote> Votes { get; set; }
public Comment()
{
CreateDate = DateTime.UtcNow;
Hidden = false;
}
}
Here is my DataContextInitializer
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Comment>().HasMany(i => i.Replies)
.WithOptional(i => i.Comments)
.HasForeignKey(i => i.RepliesId)
.WillCascadeOnDelete();
}
You could enable cascade delete with something like this (i.e. you need to manually set the relationship)...
modelBuilder.Entity<Comment>()
.HasOptional(x => x.Replies)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
However, it won't do you much good - as Update-Database will fail with something like...
A foreign key constraint that has an UPDATE or a DELETE CASCADE rule,
and self-references a column in the same table, is not allowed.
i.e. that works on FK-s that are connecting different tables - but not if self-referencing.
See this post with some more relevant info - especially the comment that mentions
"you need to drop the FK and manually create it with cascade delete in
your DatabaseInitializer"
EF4 Code first - delete all children when deleting parent from db?
In short, I don't think there is a straight-forward solution - but some manual setup (initializer etc.) is required (I haven't tried it). Or try to reorganize, flatten the relationships a bit (I haven't thought much, just throwing here some pretty general approaches).
Just FYI - even though I think it's not going to get you anywhere (see above)...
public class Comment
{
// Properties
public long Id { get; set; }
//[Required]
//[StringLength(250, ErrorMessage = "{0} must be between {1} and {2} characters", MinimumLength = 2)]
public string Body { get; set; }
[Required]
public DateTime CreateDate { get; set; }
// [Required]
// [InverseProperty("Comments")]
public MyUser Author { get; set; }
// [InverseProperty("CommentCount")]
public Blog Blog { get; set; }
public bool Hidden { get; set; }
public virtual ICollection<Comment> Replies { get; set; }
public virtual ICollection<Vote> Votes { get; set; }
public Comment()
{
CreateDate = DateTime.UtcNow;
Hidden = false;
}
}
modelBuilder.Entity<Comment>()
.HasOptional(x => x.Replies)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
This should work fine if you let it 'not cascade'. Otherwise fails.
I am working with the EF Code First library trying to work on an appointment scheduling app.
The model's I have are going to be a Client, Appointment, and AppointmentType...
Basically each Client can have a set of Appointments, and each Appointment can have a single AppointmentType...
The code is as follows:
public class Client
{
[ScaffoldColumn(false)]
public int ClientID { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[EmailAddress]
[Required]
public string Email { get; set; }
[DataType("DateTime")]
public DateTime Birthday { get; set; }
[Required]
public string CellPhone { get; set; }
public string HomePhone { get; set; }
public string Notes { get; set; }
public virtual ICollection<Appointment> Appointments{ get; set; }
public string Name {
get{
return FirstName + " " + LastName;
}
}
public class Appointment
{
[ScaffoldColumn(false)]
public int AppointmentID { get; set; }
[ScaffoldColumn(false)]
public int ClientID { get; set; }
[ScaffoldColumn(false)]
public int AppointmentTypeID { get; set; }
[Required]
public DateTime AppointmentDate { get; set; }
public string Notes { get; set; }
public virtual AppointmentType AppointmentType { get; set; }
public virtual Client Client { get; set; }
}
public class AppointmentType
{
[ScaffoldColumn(false)]
public int AppointmentTypeID { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public virtual Appointment Appointment { get; set; }
}
Everything works well when I create an appointment type, and a client, but when I create an appointment I get the following error...
InnerException {"The INSERT statement conflicted with the FOREIGN KEY constraint \"Appointment_Client\". The conflict occurred in database \"Salon.Models.SalonContext\", table \"dbo.Clients\", column 'ClientID'.\r\nThe statement has been terminated."} System.Exception {System.Data.SqlClient.SqlException}
If more details are needed, please let me know...I am just trying to figure out if I am missing anything in the setup.
This is what happens when I debug on the post to create the Appointment...All the ID's are as 0 which is correct, but should the other fields not be null?? Or does it matter...Just not very familiar with how things should look this being my first EF Code First project...
According to your setup, one AppointmentType can only have one Appointment. This is a one-to-one mapping. In this case, you better move the AppointmentType into the Appointment entity. Otherwise, what I believe is more logical, an AppoitmentType can have many Appointments but one Appointment can have only one AppointmentType. Accordingly, you should have a virtual ICollection inside your AppointmentType entity.
public class AppointmentType
{
[ScaffoldColumn(false)]
public int AppointmentTypeID { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
}
I am not sure this is what's causing the problem but it could be. Sometimes mapping faults cause some weird exceptions to be thrown. Give it a try and let me know if your problem gets resolved.
By your constraints AppointmentType and Client cannot be null in Appointment. You can delete constraints or set correct objects in object properties. For example create Client and AppointmentType and then create Appointment for created Client with created AppointmentType
I'm building a personal blog app with Entity Framework 4 CTP 5 POCO only, where a Post can have many Tags and a Tag can be in many Posts. My question is whether to build a many-to-many model, or to have a lookup table.
At first I was trying to use many-to-many, but I don't know how to do insertion, each time a new post is posted (with many tags selected), I'm not sure what to do so that the tags should be associated with the post (and wouldn't insert a new tag if the tag name already exists.)
I then try to build a Lookup table like so:
Post
public class Post
{
public int Id { get; set; }
[Required]
[StringLength(512, ErrorMessage = "Title can't exceed 512 characters")]
public string Title { get; set; }
[Required]
[AllowHtml]
public string Content { get; set; }
public string FriendlyUrl { get; set; }
public DateTime PostedDate { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual ICollection<PostTagLookup> PostTagLookups { get; set; }
}
Tag
public class Tag
{
public int Id { get; set; }
[Required]
[StringLength(25, ErrorMessage = "Tag name can't exceed 25 characters.")]
public string Name { get; set; }
public string FriendlyName { get; set; }
public virtual ICollection<PostTagLookup> PostTagLookups { get; set; }
}
PostTagsLookup
public class PostTagLookup
{
public int PostId { get; set; }
public int TagId { get; set; }
}
The problem is I'm not sure how EF are going to handle lookup table (how will I get the Tags of a Post, or a collection of the Posts when I select a Tag). And with the code below, I got an error saying PostTagLookup doesn't have an Id key:
var getPosts = _post.GetLatestPosts(3).ToList();
var posts = from post in getPosts
select new PostModel
{
Id = post.Id,
Title = post.Title,
Content = post.Content,
FriendlyUrl = post.FriendlyUrl,
PostedDate = post.PostedDate,
IsActive = post.IsActive,
NumberOfComments = post.Comments.Count(),
PostTags = post.PostTagLookups.Where(p => p.PostId == post.Id).ToList()
};
Any suggestion on how to accomplish this task? Thank you very much!
I think your model should work as-is with a slight tweak: add an ID column/field to the PostTagLookup entity.
public class PostTagLookup
{
[Key]
[DatabaseGenerated(DatabaseGenerationOption.Identity)]
public int PostTagLookupId { get; set; }
//etc.
}
However, I'm not sure why you wouldn't want EF to handle the underlying many-to-many on it's own. When you have a new Post object, for example, all you have to do is add any associated Tags to the instantiated Post's Tags collection before calling SaveChanges() on your context. Did this not work for you?
I've struggled with it for the past couple days and decided to go with the Lookup table as intended, and in the Lookup table, also have references to Post and Tag model as such:
public class PostTagLookup
{
public int Id { get; set; }
public int PostId { get; set; }
public int TagId { get; set; }
public virtual Post Post { get; set; }
public virtual Tag Tag { get; set; }
}
Might not be the best way but it's working :) Thanks all for looking.