I have two entities, Customer and User as follows:
public class User
{
[Key]
public int Id { get; set; }
[StringLength(20)]
public string CustomerId { get; set; }
public string Password { get; set; }
public bool Locked { get; set; }
[ForeignKey("CustomerId")]
public virtual Customer Customer { get; set; }
}
public class Customer
{
[Key]
[Column("Id", TypeName = "nvarchar")]
[StringLength(20)]
public string Id { get; set; } // nvarchar(20)
[Required]
public string GivenName { get; set; } // nvarchar(100)
[Required]
public string Surname { get; set; } // nvarchar(100)
public virtual ICollection<User> Users { get; set; }
}
I have a simple strong typed view for editing a customer, and I want to add to the view a check-box with following logic - the check-box should be selected, when there is at least one user for that customer and the Locked property of the first user is set to false. I just can't find a way to accomplish this. What's the proper way to do this in MVC? And how the processing method (the [HttpPost]Edit) receives the value of this check-box, currently it simply gets the Customer object? Should I create an additional model for this view? Or there is another way?
Anticipating this question I should say that I'm taking care that there wont be more than one user for a customer.
Updates:
I've added a view model for customer and updated the edit view and the controller to work with this model:
public class CustomerViewModel
{
public Model.Data.Customer BaseCustomer { get; set; }
public bool HasActiveUser { get; set; }
}
My edits saving method looks now like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(ViewModel.Data.Customer customer)
{
if (ModelState.IsValid)
{
//db.Entry(customer.BaseCustomer).Collection("Users").Load();
db.Entry(customer.BaseCustomer).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CustomerTypeId = new SelectList(db.CustomerTypes, "Id", "Name", customer.BaseCustomer.CustomerTypeId);
return View(customer);
}
The only question remains is how do I access the Users navigation property which is null, I've tried to reload it but got an InvalidOperationException with error that reads Member 'Load' cannot be called for property 'Users' because the entity of type 'Customer' does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet<Customer>. I've also tried to get the Customer again with Customer baseCustomer = db.Customers.Find(customer.Id); but then I can't set db.Entry(customer.BaseCustomer).State = EntityState.Modified; since it tells me that An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. Any ideas, please?
I decided to go with creating a dedicated view model that will contain the domain model object(as suggested as second pattern ASP.NET MVC View Model Patterns by Steve Michelotti) and an additional property for binding to my check-box. Then in the controller I handle all the logic regarding when to show the check-box selected, and when to create a new user if one not exists. I've encountered several problems so I want to post my solutions, maybe they are far from best-practices but I think they might be of use to beginners, and I definitely would like to see comments or other solutions.
Objects aren't persisted if they don't participate in view since EF recreates them on post-back from the data received from the view. This prevented me from adding a User property that will be an accessor for the first User in navigation collection Users property of Customer (when I added it, it couldn't access Users since this property is null after post-back, as I understood this is because of the recreated Customer is detached from context).
In order to be able to use navigation properties I had to attach the recreated(by EF, as explained earlier) Customer object to the context by setting db.Entry(customer.BaseCustomer).State = EntityState.Modified;(thanks to Using DbContext in EF 4.1 Part 4: Add/Attach and Entity States ยง Attaching an existing but modified entity to the context) and to reload the collection by calling db.Entry(customer.BaseCustomer).Collection("Users").Load();
Related
I have a class like the following:
public sealed class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; private set; }
[Required] public string GroupId { get; set; }
[Required] public bool IsAdmin { get; set; }
}
And another:
public sealed class Group
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; private set; }
[ForeignKey("GroupId")]
public List<User> Users { get; set; }
}
The IsAdmin property of the User class signifies whether or not the user is an admin. What I want to do is add a new property to Group:
public List<User> Admins { get; set; }
This new list will contain all users that are admins of their group, meaning that their property IsAdmin has value true. I have considered using a custom getter for this property, like so:
public List<User> Admins
{
get
{
return this.Users.Where(user => user.IsAdmin);
}
}
However, I would like to know if Entity Framework can take care of this for me. In my head I can imagine it using IsAdmin in a way similar to how GroupId is used for the Users list, where every user that has User.GroupId = "foo" is included in the Users list of the group with Group.Id = "foo".
So my question is, how do I tell EF to use the IsAdmin property of users as a foreign key to populate Admins?
So firstly, using the term "Foreign Key" in this context is wrong. IsAdmin is not a foreign key, at best it is a discriminator.
Secondly, you can use the [NotMapped] attribute like this
[NotMapped]
public List<User> Admins
{
get
{
return this.Users.Where(user => user.IsAdmin);
}
}
so that EF ignores that property and doesn't try to create any relationships with it, That way you will get the values you want lazyloaded when you access Admins.
Finally, I think you have your data structure all wrong. Unless a User could only ever be a member of one group OR being an admin in one group made them admins across all groups that they were a member of, then your structure would make sense from a domain perspective but still be wrong from a data perspective.
What I advise that you do is view the Admin <-> User relationship as Many to many and introduce an intersect object GroupAdmins which would have the Id of the group and the Id of the user. You can have that intersect table created automatically by EF giving you a simpler domain model or you could do it manually, See article for the former here.
Re-reading, your question, the above doesn't apply, however, I'll leave it here in case someone with a similar situation happens upon this answer.
I'm using Entity Framework with Web API 2. I have a boat entity with properties like name, price etc. Those simple properties update fine when sent by Put to the web api controller. The boat entity also has a many to one relationship with an entity called BoatType. Boat types are "Yacht", "Motor Yacht" etc.
When a boat entity is updated in the controller the foreign key for boat type in the database doesn't get updated. Do I have to somehow manually update the child entity value or is there a way to get EF to do this automatically?
Here's an example PUT request sent to web API:
{
"$id":"1",
"Images":[],
"BoatType": {
"$id":"3",
"Boat":[],
"Id":1,
"DateCreated":"2015-09-15T13:14:39.077",
"Name":"Yacht"
},
"Id":2,
"Name":"Schooner",
"Description":"A harmless schooner",
"DateCreated":"2015-09-15T17:59:37.8",
"Price":65000
}
Here's the update function in web API:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> Put(int id, Boat boat)
{
if (id != boat.Id)
{
return BadRequest();
}
_db.Entry(boat).State = EntityState.Modified;
try
{
await _db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!BoatExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
I've looked at similar questions like Entity Framework Code First Update Does Not Update Foreign Key, Entity Framework does not update Foreign Key object and Update foreign key using Entity Framework but none seem to have quite the same scenario (or the answers didn't help me understand my issue).
Here's the Boat and BoatType model classes (auto-generated by EF designer).
public partial class Boat
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Boat()
{
this.Images = new HashSet<Image>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public System.DateTime DateCreated { get; set; }
public Nullable<double> Price { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Image> Images { get; set; }
public virtual BoatType BoatType { get; set; }
}
public partial class BoatType
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public BoatType()
{
this.Boat = new HashSet<Boat>();
}
public int Id { get; set; }
public System.DateTime DateCreated { get; set; }
public string Name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Boat> Boat { get; set; }
}
Ok, I figured out what the problem was. Using SQL Server Profiler to look at the SQL Update statement I saw that the foreign key for Boat.BoatType wasn't even in there - so I figured my model must be screwed up somewhere. When I created the model in the designer, I mistakenly set the relationship between Boat and BoatType as one to one. I later realised the mistake and changed the association to one (BoatType) to many (Boats) but that must have been AFTER I generated the database. D'oh! Something about the way EF handles associations meant that simply changing the association type in the diagram wasn't enough - I should have dropped/recreated the database constraint at that time.
Since I only had test data in the database what worked for me was to recreate the database using the "Generate database from model..." option in the designer.
Once I got the PUT working correctly the other thing I had to solve (which is not really on topic for this question but it's been discussed above so just in case it's useful to someone) was that Web API gave the error "A referential integrity constraint violation occurred: The property value(s) of 'Boat.BoatTypeId' on one end of a relationship do not match the property value(s) of 'Boat.BoatType.Id' on the other end.". The select list that allows the user to change the boat type is bound on the client using AngularJS to Boat.BoatType. So in the PUT data, Boat.BoatType had been updated to new values but Boat.BoatTypeId hadn't changed - hence the "referential integrity" error. So I just manually set the value of Boat.BoatTypeId to Boat.BoatType.Id before sending the PUT and all works as expected now.
I've been searching for a way to post all the information of a model which contains other models and I believe I can just send the object to my view and go off of the 50 examples I've looked at and can render everything just fine.
Here's my model I'm talking about named Equipment.
public int id { get; set; }
public String name { get; set; }
public ManufacturerItem manufacturerItem { get; set; }
public EquipmentType equipmentType { get; set; }
public SupportItem supportItem{ get; set; }
public Placement placement{ get; set; }
public Boolean status { get; set; }
public DateTime endOfLife{ get; set; }
public String notes{ get; set; }
public Purchase purchase{ get; set; }
public Boolean mes{ get; set; }
public DateTime reviewedDate{ get; set; }
Based on the tons of examples I've read I know I can render these like this:
#Html.EditorFor(model => model.name)
#Html.EditorFor(model => model.manufacturerItem.model.name)
In other research I did stumble upon building forms for deep View Model graphs in ASP.NET MVC which I may consider in using, but that was posted back in MVC 2 days. I'm using MVC 5. So I don't know how relative that is today.
So let's say I have another model named Book with {id, Title, Author} and you could edit the book name and author. Now in this model, on edit, my controller could be as such:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,Author)"] Book book)
{ ... -insert code- ...}
Going off of this idea, what would be my controller method signature be for the Equipment model? Do I include the other objects as their own types?
I'm not using EF or linq-to-sql because I have to use stored procedures. So I want to get all this information neatly packaged and passed off to the repository that will take care of parameter assignment and calling of the stored procedure.
Going off of this idea, what would be my controller method signature
be for the Equipment model?
Have you tried using the following signature:
[HttpPost]
public ActionResult Edit(Equipment model)
{
...
}
By the way if your view doesn't contain a form allowing to edit all the properties of the Equipment model object graph you may consider using a view model containing only the properties that are included as input fields in your form. Then on the server you will get the corresponding Equipment instance from your backend using the id, update only the properties that were sent from the HTML form and save the results back.
For example:
[HttpPost]
public ActionResult Edit(EquipmentViewModel model)
{
Equipment equipement = backend.GetById(model.Id);
// set the properties that are coming from the UI:
equipment.name = model.Name;
equipment.supportItem = model.SupportItem;
...
// save the updated entity back
backend.Update(equipment);
}
In this example the EquipmentViewModel will contain only the properties that you have corresponding input fields in your view and which the user is supposed to edit and not the entire domain model object graph.
I have fields that the customer can add or remove in a list on their application and need to therefor add them to the database / context for later use.
For example I have a model first entity context dbContext and the model in question is Customers with properties id, name, accesslevel, ...
What i want to know is if there is a way for me to programmatically add a property to the Entity Model / or context or if I need to take an entirely different approach to dynamic database fields?
Ex: Needing to add a Inactive boolean field to it's Model / and Database table.
No, that's definitely not possible. EF doesn't support it and even if you could, adding new properties to your models would require a database migration every time. If you want the user to be able to add custom data to the Cutomers class, consider something like this:
public class Customers
{
public int Id { get; set; }
public string Name { get; set; }
//... more properties.
public virtual ICollection<UserData> UserData { get; set; }
}
public class UserData
{
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
}
Otherwise, the only way you could do something like this is with a NoSQL database like MongoDB.
You can find the source code demonstrating this issue # http://code.google.com/p/contactsctp5/
I have three model objects. Contact,ContactInfo,ContactInfoType. Where a contact has many contactinfo's and each contactinfo is a contactinfotype. Fairly simple I guess. The problem I'm running into is when I go to edit the contact object. I pulled it from my contact repository. Then I run "UpdateModel(contact);" and it updates the object with all the values from my form. (monitoring with debug) When I save the changes though, I get the following error:
The operation failed: The relationship
could not be changed because one or
more of the foreign-key properties is
non-nullable. When a change is made to
a relationship, the related
foreign-key property is set to a null
value. If the foreign-key does not
support null values, a new
relationship must be defined, the
foreign-key property must be assigned
another non-null value, or the
unrelated object must be deleted.
It seems like after I call update model it nulls out my references and this seems to break everything? Any thoughts on how to remedy would be greatly appreciated. Thanks.
Here are my models:
public partial class Contact {
public Contact() {
this.ContactInformation = new HashSet<ContactInformation>();
}
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<ContactInformation> ContactInformation { get; set; }
}
public partial class ContactInformation {
public int ContactInformationId { get; set; }
public int ContactId { get; set; }
public int ContactInfoTypeId { get; set; }
public string Information { get; set; }
public virtual Contact Contact { get; set; }
public virtual ContactInfoType ContactInfoType { get; set; }
}
public partial class ContactInfoType {
public ContactInfoType() {
this.ContactInformation = new HashSet<ContactInformation>();
}
public int ContactInfoTypeId { get; set; }
public string Type { get; set; }
public virtual ICollection<ContactInformation> ContactInformation { get; set; }
}
My Controller Action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Contact person) {
if (this.ModelState.IsValid) {
var contact = this.contactRepository.GetById(person.ContactId);
UpdateModel(contact);
this.contactRepository.Save();
TempData["message"] = "Contact Saved.";
return PartialView("Details", contact);
} else {
return PartialView(person);
}
}
Context Code:
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) {
modelBuilder.Entity<Contact>()
.HasMany(c => c.ContactInformation)
.WithRequired()
.HasForeignKey(c => c.ContactId);
modelBuilder.Entity<ContactInfoType>()
.HasMany(c => c.ContactInformation)
.WithRequired()
.HasForeignKey(c => c.ContactInfoTypeId);
}
There's a few things going on here.
1 If you are set up for lazy loading child objects are only loaded if you tell them to load. This can be done with the following in your query.
..
context.Contacts.Include(c => c.ContactInfos).Include(c => c.ContactInfos.ContactInfoType)
see this article for full details on making sure objects are loaded as you want.
2 If you don't want to save contactinfo and contactinfotype (because they are not loaded or you just don't want to), you will need to tell the context not to save child objects that shouldn't be updated. This can be done using:
..
context.StateManager.ChangeObjectState(entity.ContactInfos.ContactInfoType, EntityState.Unchanged);
I find I need to do that when changing/using a country object to user data. I definitely never want that to be updated by a user.
In the middle of writing a bit of a guide to all this, but could be weeks until it's done on my blog
3 MVC won't store/send back anything you don't put into the form. If you send an object heirarchy to the form and the values aren't represented in hidden inputs, they will come back empty on your model. For this reason, I generally make viewmodels that are editable only versions of the entities with a ToEntity and a ToModel method on them. This also covers me for security as I don't want all sorts of user ids in hidden inputs, just so my entities map straight into MVC (see this article on overposting).
I WOULD have thought that the fact you have your contactinfo properties set to virtual, the UpdateModel wouldn't mind if they didn't exist on the return, but I could well be wrong as I haven't tried it.
I figured this question out thanks to Morteza Manavi on the entity framework website. My issue was caused by my ContactInformation model properties, 'contactid' & 'contacttypeid' not being nullable. Once I fixed this everything with UpdateModel() worked correctly. Thank you very much!