I have the properties for two entities in a ViewModel. The two entities are both related to one another, so for example, User and Posts. Each User can have multiple Posts, and Many Posts can belong to a single user (one-to-many).
The aim from my ViewModel is to allow the addition of a User and a Post on the same form. So my ViewModel looks something like this:
public class CreateVM
{
[Required, MaxLength(50)]
public string Username { get; set; }
[Required, MaxLength(500), MinLength(50)]
public string PostBody { get; set; }
// etc with some other related properties
}
In my Controller on the Create Method I have something like this:
[HttpPost]
public ActionResult Create(CreateVM vm)
{
if (ModelState.IsValid)
{
User u = new User()
{
Username = vm.Username,
// etc populate properties
};
Post p = new Post()
{
Body = vm.PostBody,
// etc populating properties
};
p.User = u; // Assigning the new user to the post.
XContext.Posts.Add(p);
XContext.SaveChanges();
}
}
It all looks fine when I walk through it through the Debugger, but when I try to view the post, its User relationship is null!
I also tried
u.Posts.Add(p);
UPDATE:
My Post class code is as follows:
public class Post
{
[Key]
public int Id { get; set; }
[Required, MaxLength(500)]
public string Body { get; set; }
public int Likes { get; set; }
[Required]
public bool isApproved { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
[Required]
public User User { get; set; }
}
But that also did not work. What am I doing wrong?
Problem is that EF can not lazy load the User property because you haven't made it virtual.
public class Post
{
[Key]
public int Id { get; set; }
[Required, MaxLength(500)]
public string Body { get; set; }
public int Likes { get; set; }
[Required]
public bool isApproved { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
[Required]
public virtual User User { get; set; }
}
If you know beforehand that you are going to access the User property of the post you should eager load the User related to the post.
context.Posts.Include("User").Where(/* condition*/);
Related
I'm currently learning ASP.NET MVC and Web API.
I'm trying to create a User Model. Users can have any number of UserContacts. UserContacts reference the User it is a contact of and the User who is the contact. I have made a model called UserContact because attached to this Model is additional information.
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
[ForeignKey("UserID"), Column(Order = 0)]
[Required]
public User User { get; set; }
public int ContactID { get; set; }
[ForeignKey("ContactID"), Column(Order = 1)]
[Required]
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
So this gives me an error referring to cascading Delete. How do I set up a relationship like this where two foreign keys point to the same Model type? I have yet to grasp Entity Framework syntax as well. If I don't have an ICollection of UserContacts in the User model, does this hinder my ability to grab the UserContacts associated with that User?
When you have a foreign key and the foreign key columns are not nullable(means,required). EF will automatically tries to enable cascading delete on the relationsip. In your case, it will try to enable Cascading delete for both the foreign key columns and both of them points to the same user table! That is the reason you are getting this error. What if you have a UserContact record with Both UserId and ContactID points to the same User record. Cascading delete is confused now :)
Also, since one user can have more than one Contacts, We need a Contacts property on the User table to represent that. This will be a collection of UserContact's. Also this user can be a a contact of many other people. So Let's create another property for that.
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<UserContact> Contacts { set; get; }
public ICollection<UserContact> ContactOf { set; get; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
public User User { get; set; }
public int ContactID { get; set; }
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
And in your DbContext class, We can configure the foreign key relation ships and tell EF to disable cascade delete using fluent configuration inside the overridden OnModelCreating method. The below code will disable cascading delete on both the the relationships. But for your error to go away. disabling on one foreign key is enough.
public class YourDbContext: DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserContact>()
.HasRequired<User>(g=>g.User)
.WithMany(g=>g.Contacts)
.HasForeignKey(g=>g.UserID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserContact>()
.HasRequired<User>(g => g.Contact)
.WithMany(g => g.ContactOf)
.HasForeignKey(g => g.ContactID)
.WillCascadeOnDelete(false); // this one is not really needed to fix the error
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { set; get; }
public DbSet<UserContact> UserContacts { set; get; }
}
This will create the tables like you wanted with the necessary foreign keys.
There is not enough information for EF to figure out the relationships on the other side, so yes, you need collections. You can use the InverseProperty annotation to clarify (or fluent api statements):
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[InverseProperty("User")]
public Virtual ICollection<UserContact> Users{ get; set; }
[InverseProperty("Contact")]
public Virtual ICollection<UserContact> Contacts { get; set; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
[ForeignKey("UserID"), Column(Order = 0)]
[Required]
public User User { get; set; }
public int ContactID { get; set; }
[ForeignKey("ContactID"), Column(Order = 1)]
[Required]
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
http://www.entityframeworktutorial.net/code-first/inverseproperty-dataannotations-attribute-in-code-first.aspx
after looking for an answer in the already existing questions, I am still a little confused as how I should proceed. I am new to the MVC 3 framework, so if I come off sounding like a dope, I appologize!!
Ok, so I created a MVC 3 internet application, created 3 new Users administrator, user1, and user2. I have created a new model, and controller for my "Posts" I am able to add, edit and delete the items. I currently have a column called UserID in my posts table. I would like this to be automagically populated with the current UsersID. I think I would define this in the controller like this:
[HttpPost]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
public int User = System.Web.Security.Membership.GetUser(id);
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
Inside the model, this is what I currently have:
public class Post
{
public int PostID { get; set; }
public int UserID { get; set; }
public string PostTitle { get; set; }
public int PostType { get; set; }
public string PostBody { get; set; }
public string PostBlogTitle { get; set; }
public string PostBlogURL { get; set; }
public string PostCategory { get; set; }
public string PostSEO { get; set; }
public int PostStatus { get; set; }
}
public class PostDBContext : DbContext
{
public DbSet<Post> Posts { get; set; }
}
I would like to replace public int UserID { get; set; } with my newly defined variable in the controller, but not sure where/how to add it.
Not sure I'm clear on what you're trying to achieve. Are you just trying to assign the current user to the model, save it and then pass it back to the view?
If so you can just do this:
[HttpPost]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
// Note: I'm assuming this actually works/returns an int
public int User = System.Web.Security.Membership.GetUser(id);
post.UserID = User;
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
I have three related tables. Calendar 1...* CalendarUser *...1 User. When I have edited the CalendarUsers in the edit calendar view I then post the ViewModel back to the controller. Here is my controller code:
[HttpPost]
public ActionResult Edit(int id, CreateCalendarViewModel cvm)
{
long userId = long.Parse(User.Identity.Name);
db.Calendars.Attach(cvm.CurrentCalendar);
cvm.Users= DbExtensions.GetUserList(userId);
if (ModelState.IsValid)
{
////Remove the deselected users
cvm.CurrentCalendar.CalendarUsers.Clear();
//Get the names from the selected users
var selectedUsers = from u in cvm.Users
where cvm.SelectedUsers.Contains(u.Key)
select new KeyValuePair<long, string>(long.Parse(u.Key), u.Value);
foreach (var selectedUser in selectedUsers)
{
User user = db.Users.Find(selectedUser.Key);
//If usr does not exist create a new
if (user == null)
{
db.Users.Add(new User
{
UserId = selectedUser.Key,
Name = selectedUser.Value,
Expires = DateTime.Now,
AccessToken = string.Empty
});
}
//Add the binding to the calendar
cvm.CurrentCalendar.CalendarUsers.Add(new CalendarUser
{
CalendarId = cvm.CurrentCalendar.CalendarId,
UserId = selectedUser.Key
});
}
db.Entry(cvm.CurrentCalendar).State = EntityState.Modified;
db.SaveChanges();
}
return View(cvm);
}
Here are my classes:
public partial class Calendar
{
public Calendar()
{
this.CalendarUsers = new HashSet<CalendarUser>();
}
public int CalendarId { get; set; }
public string CalendarTitle { get; set; }
public string CalendarDescription { get; set; }
public long UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<CalendarUser> CalendarUsers { get; set; }
}
public partial class CalendarUser
{
public int CalendarUserId { get; set; }
public int CalendarId { get; set; }
public long UserId { get; set; }
public Nullable<bool> IsAdmin { get; set; }
public virtual Calendar Calendar { get; set; }
public virtual User User { get; set; }
}
public partial class User
{
public User()
{
this.Calendars = new HashSet<Calendar>();
this.CalendarUsers = new HashSet<CalendarUser>();
}
public long UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Calendar> Calendars { get; set; }
public virtual ICollection<CalendarUser> CalendarUsers { get; set; }
}
For some reason when i save the changes the calendar is being deleted as well? I've searched a bit but noone seem to have the same problem? Am I doing it wrong? Is there a better way to update/remove related entities?
It seems that I forgot to include a hidden field in in the view containing the id of the user and the result was that when I updated the calendar it saved with Id = 0 and thus hid the objects in the view for the specified user. Mental note: Always verify in the database what is really happening.
I also need to look into whats happening when I send objects back and forth between views and controller. Sometimes it seems to manage by itself and sometimes I need to specify all the fields myself.
I have a class, which has 8 props / 8 columns in DB. But on a Edit page, i dont want to show the AddedDate or UserID field, since i dont want user to change it.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime AddedDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
Here is what I have for Edit controller:
// POST: /Voucher/Edit/5
[HttpPost]
public ActionResult Edit(Voucher voucher)
{
if (ModelState.IsValid)
{
string[] excludeProperties = { "AddedDate", "UserID" };
UpdateModel(ModelState, "", null, excludeProperties);
db.Entry(voucher).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(voucher);
}
On Edit page, once i click on submit, i got the following error: System.Data.SqlServerCe.SqlCeException: An overflow occurred while converting to datetime.
Seems like the AddedDate didn't get excluded from the view model and triggered the error.
Would you please let me know how to fix it? Thanks!
public ActionResult Edit([Bind(Exclude = "AddedDate")]Voucher voucher)
no luck either
You are still passing in Voucher which could contain that field in it. I'm not sure what you are trying to accomplish with the UpdateModel here if you are already passing in a Voucher object?
Pass in Voucher, set it to modified and save it. If you want to use whats in the database then you'll have to
Load the object from the database
UpdateModel and exclude the properties
Save your entity.
You could simply use a View Model and post that.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
and then load up your object from the db":
var voucher = db.Vouchers.Where(o=>o.ID==voucherViewModel.Id);
//manually copy the fields here then save it
//copy
db.SaveChanges();
My project has two objects: users and meetings
Every meeting has one user that is the "head" of the meeting and a many users that are simple.
My models are these:
public class Meeting
{
public int MeetingId { get; set; }
public string Title { get; set; }
public virtual User User { get; set; }
public Location From { get; set; }
public Location To { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class Location
{
public float Lat { get; set; }
public float Long { get; set; }
}
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
I created a controller for the meeting model. Now every time that i add another meeting a and in the user field i put an existing userid this user is not inserted and new user is created.
What's wrong?
edit
the create controller
[HttpPost]
public ActionResult Create(Tremp tremp)
{
if (ModelState.IsValid)
{
db.Tremps.Add(tremp);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(tremp);
}
in the form i just enter the id of the user
I'm no expert on EF code first, but your code example did cause some deja vu for me. I think the problem is that you need to configure the relationship between the "head" user and the meeting e.g.
public class Meeting
{
public int MeetingId { get; set; }
public string Title { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
[Column(name: "UserId")]
public int HeaderUserId { get; set; }
public Location From { get; set; }
public Location To { get; set; }
public virtual ICollection<User> Users { get; set; }
}
If your tremp entity has a reference to an existing user (tremp.User) you must attach this user to the context before you add the tremp. This tells EF that this user is existing in the database and avoids to insert a new user:
[HttpPost]
public ActionResult Create(Tremp tremp)
{
if (ModelState.IsValid)
{
db.Users.Attach(tremp.User);
db.Tremps.Add(tremp);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(tremp);
}