I'm trying to update a model, but get the 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."
From what I understand from The relationship could not be changed because one or more of the foreign-key properties is non-nullable the problem might be with how Entity Framework handles my virtual ICollection
However I'm not really sure how to implement the solution when using scaffolded repository pattern. Do I have to edit the Save()-method ParentObjectRepository-class?
Actually I really think that there must be some way to make EF understand this. I can't see how the EF-team was thinking "Probably noone is using a collection of objects with a foreign key constraint, lets not support that".
Update
Added code
[HttpPost]
public ActionResult Edit(int id, FormCollection formCollection)
{
var eventRepository = new MagnetEventRepository();
var original = eventRepository.Find(id);
UpdateModel(original);
eventRepository.Save();
return RedirectToAction("Details", "Home", new { slug = original.Slug });
}
public void Save()
{
context.SaveChanges();
}
More code:
public class MagnetEvent
{
public virtual int Id { get; set; }
[Required]
public virtual string Name { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd HH:mm}")]
[DataType(DataType.DateTime)]
public virtual DateTime? StartDate { get; set; }
public virtual string Description { get; set; }
[StringLength(100)]
public virtual string Slug { get; set; }
public virtual int MaximumCapacity { get; set; }
[DataType(DataType.Currency)]
public virtual int TicketPrice { get; set; }
public virtual int LocationId { get; set; }
public virtual Location Location { get; set; }
public virtual Collection<Ticket> Tickets { get; set; }
public virtual Collection<AttendeeInformationField> CaptureAttendeeInformationFields { get; set; }
public virtual int CustomerId { get; set; }
[Required]
public virtual CUSTOMER Customer { get; set; }
}
The Save()-method is from MagnetEventRepository, which is scaffolded from the above class.
Another update
I successfully removed the error by changing MagnetEventId in AttendeeInformationField to nullable int. When examining the database I can see exactly what's wrong.
Let's say I have a single AttendeeInformationField with the value "E-mail". When I edit my MagnetEvent, the AttendeeInformationField updates the MagnetEventId to null and then adds a new post with the correct MagnetEventId and Value.
I'd very much prefer if the posts in AttendeeInformationField were updated instead.
can you add the code for your event object. The one you call original.
It might be so that the UpdateModel change some info on the associated objects and that's not good if so. Not sure about this though I can't see all the code.
I prefer to not uder UptadeModel and instead use a inputmodel or your MVC model as the inparameter and manually map the chages to the loaded original object.
Antoher problem is that I can't see if
eventRepository.Save();
really do an SaveShages? does it? I can se some context code in another method Save?
As the exception say it seams like your associated collections or other associated objects cant find a valid ID value.
Are you Eager-loading the associated objects? like Customer?
One thing of note is that you shouldn't have the [Required] on Customer as its inferred from the fact that your FK isn't nullable. Required should only be used on a navigation property if you do not have the FK in the model.
To try to diagnose the issue, can you load the object and look at it in a debugger, you should expect that both locationId and CustomerId have non-zero values.
I found a solution to my problem. It seems to be a bug (?) in ASP.NET MVC when it comes to UpdateModel and a model containing an ICollection.
The solution is to override the default behaviour, as described in this blog post: http://www.codetuning.net/blog/post/Binding-Model-Graphs-with-ASPNETMVC.aspx
Update
I found a solution! The above only worked when updating existing items in the collection. To solve this, I have to manually check and add new AttendeeInformationFields. Like this:
[HttpPost]
public ActionResult Edit(int id, MagnetEvent magnetEvent)
{
var eventRepository = new MagnetEventRepository();
var original = eventRepository.Find(id);
UpdateModel(original);
foreach (var attendeeInformationField in magnetEvent.CaptureAttendeeInformationFields)
{
var attendeeInformationFieldId = attendeeInformationField.Id;
if (original.CaptureAttendeeInformationFields.AsQueryable().Where(ai => ai.Id == attendeeInformationFieldId).Count() == 0)
{
original.CaptureAttendeeInformationFields.Add(attendeeInformationField);
}
}
eventRepository.Save();
}
Together with the modified DefaultModelBinder, this actually works with both editing and adding. For now I haven't tried deleting.
Still, I hope there is a simpler way to do this. Seems like a lot of coding to do a very basic task.
Related
I have these two models:
public partial class Country
{
public Country()
{
this.Dinners = new HashSet<Dinner>();
}
public int CountryID { get; set; }
public string Name { get; set; }
public virtual ICollection<Dinner> Dinners { get; set; }
}
and
public partial class Dinner
{
public Dinner()
{
this.TRsvps = new HashSet<TRsvp>();
}
public int DinnerID { get; set; }
public System.DateTime EventDate { get; set; }
public string Description { get; set; }
public string HostedBy { get; set; }
public Nullable<int> CountryID { get; set; }
public virtual Country Country { get; set; }
}
I got a bit confused on how Entity Framework will act when a user tries to delete a parent entity (in our case it is the Country entity) that has child records (Dinners).
For example if I have the following code inside my mvc action method:-
public ActionResult DeleteConfirmed(int id)
{
Country country = db.Countries.Find(id);
db.Countries.Remove(country);
db.SaveChanges();
return RedirectToAction("Index");
}
And exception will be raised if I try to remove a country which has dinners, which sounds valid.
I tried modifying my code as follow, by including the Dinners when retrieving the Country object:
Country country = db.Countries.Include(a => a.Dinners).Single(a2 => a2.CountryId = id);
db.Countries.Remove(country);
db.SaveChanges();
return RedirectToAction("Index");
No exception will be raised, so I thought that EF would have deleted the child dinners, but what happens is that it updates the countryID FK inside the Dinners table to be null.... (Cascade Set to Null)
I tried looping over the Dinners collection as follows:
public ActionResult DeleteConfirmed(int id)
{
Country country2 = db.Countries.Find(id) ;
foreach(var d in country2.Dinners)
{
db.Dinners.Remove(d);
}
db.Countries.Remove(country2);
db.SaveChanges();
return RedirectToAction("Index");
}
but this raised the following error:
An exception of type 'System.InvalidOperationException' occurred in
System.Core.dll but was not handled in user code
Additional information: Collection was modified; enumeration operation
may not execute.
I realized that I should explicitly call the .Tolist() on the foreach to get the parent and all its children deleted as follows:
foreach(var d in country2.Dinners.ToList())
Can anyone advice if I getting things wrong, or this is the only way to support cascade on delete using EF ?
Thanks
If you want your deletes to cascade automatically, in your OnModelCreating method, you need to manually enable it:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Country>().WillCascadeOnDelete(true);
}
You are describing the documented behaviour for cascade delete when the foreign key is nullable:
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship, and when the principal is
deleted the foreign key will be set to null.
If you want it to cascade delete then the relationship is required and the foreign key should not be nullable. Change public Nullable<int> CountryID { get; set; } to public int CountryID { get; set; }
Reference:
http://msdn.microsoft.com/en-us/data/jj591620.aspx#CascadeDelete
Additional info following your comment
You don't have to .Include to get cascade delete to work on required relationships i.e. once you have made the foreign key non-nullable. I am sure of this because that is how my application works.
I think you are observing that Remove marks your entire object graph for removal - whether required or not - in the same way that Add marks the entire graph for insertion. NB - I am not 100% sure of this bit so you should test this before you rely on it.
Further reading here:
using Dbset.Add Versus using EntityState.Added
Why Does Entity Framework Reinsert Existing Objects into My Database?
What is the difference between IDbSet.Add and DbEntityEntry.State = EntityState.Added?
I'm new to MVC and trying to add/ edit records in master detail form. Both masterid and detailid are generated by oracle on insert of record. Thus when I try to call DBContext.SaveChanges() I get error that foreign key is violated and no primary row with id '0' can be found.
Below is the class description.
public class Master
{
public int MasterID { get; set; }
public string MasterTitle { get; set; }
public virtual IList<Detail> Details { get; set; }
}
public class Detail
{
public int DetailID { get; set; }
public int MasterID { get; set; }
public string DetailName { get; set; }
public virtual Master Master { get; set; }
}
Controller code
[HttpPost]
public ActionResult Create(MASTER masterrecord)
{
if (ModelState.IsValid)
{
db.MASTER.Add(masterrecord);
db.SaveChanges();
}
...
}
The primary key (masterid) will get meaningful values only after record is inserted to database. context.SaveChanges() at this point tries to save Client records too with '0' masterid. I searched every where couldn't find anything which could be of useful.
Though of saving only Master table first so that I can retrieve the masterid and us it with DETAIL model. however couldnt find anywhere how to do it using EF5 MVC ASP.NET
Can any one point me to right direction of provide with some working sample?
thanks
Siddhant
You might want to consider using GUIDs instead of ints for your PK. Then in your constructor for Master you can say MasterID = Guid.NewGuid();. This way you don't have to hit the database to find out what the next ID will be.
There is a pro and con list here http://www.codinghorror.com/blog/2007/03/primary-keys-ids-versus-guids.html
If you have set a breakpoint on the Create method and are getting a proper list in your Master object, it may be an issue with the Oracle provider.
As a workaround, you could try to change the method signature to accept your data like the following:
public ActionResult Create(MASTER masterrecord, List<Detail> details)
Then you could first save the masterrecord and subsequently add your details and save again. It's not optimal, but it may work.
Side note: change your IList to an ICollection.
I've got this Venue object:
public class Venue
{
public int Id { get; set; }
[Required]
[MaxLength(512)]
public string Name { get; set; }
public string Description { get; set; }
[Required]
[Display(Name = "Venue Type")]
public int VenueTypeId { get; set; }
public virtual VenueType VenueType { get; set; }
[Required]
[Display(Name = "Company")]
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<VenuePart> VenueParts { get; set; }
}
As you can see, it has a collection of VenueParts. I send the Venue to the view, and output the collection of VenueParts as a table of textboxes. This gets posted back to Edit(VenueDetailsViewModel venueDetailsViewModel). Using the debugger, I can verify that my change are in the VenueParts collection, so I think we're good on binding.
My controller tries to update the Venue. It succeeds on the properties directly on the object, such as Name. But, unless I loop through the collection, it does not update those objects. Is that typical behavior?
unitOfWork.VenueRepository.Update(venueDetailsViewModel.Venue);
// Should this loop be necessary?
foreach (var venuePart in venueDetailsViewModel.Venue.VenueParts)
{
unitOfWork.VenuePartRepository.Update(venuePart);
}
unitOfWork.Save();
At the moment, I'm not even worried about handling new stuff in the list or things that vanished from the list (although that is what I am tackling next). For my first step here, I just want to get the list updated. Is it necessary to loop through the collection and update each individual object? If I don't do this, they don't save. But it seems like they ought to without my loop. Are my expectations too high or am I doing something wrong?
My repository and unitOfWork objects are patterned after this tutorial if you are curious what that code looks like.
That is because unitOfWork.VenueRepository.Update(venueDetailsViewModel.Venue); will attach the object graph in Unchanged state and only change the venue as Modified. One alternative would be to move the foreach loop to the VenuePartRepository.Update method.
If you allow elements of VenueParts to be added or removed from the UI you will have a hard time applying the changes. If this is the case you will have to load the collection in the database and compare that with the changes coming in. Then manually change the states of VenuePart to Added or Deleted.
Am having trouble finding a clear answer to my situation when searching Stack Overflow and Google, hopefully someone can point me in the right direction.
My Situation
I want to be able to use a single edit form (in a single View) to update a 3-level-deep hierarchical entity using ASP.NET MVC 3 and Entity Framework 4 CTP (Code-first) - the model consists of Services, which can have many Service Options, which in Turn can have many Inventory Items.
I was expecting to be able to use MVCs default model binder (via TryUpdateModel) to:
Update an existing 'Service' record
Add/Update/Delete 'Service Option' records (attached to the Service) depending on posted values
Add/Update/Delete 'Inventory' records (attached to each Service Option) depending on posted values
My Model
[Bind(Include="Name, ServiceOptions")]
public class Service {
[Key]
public int ServiceID { get; set; }
public string Name { get; set; }
public DateTime DateCreated { get; set; }
public virtual ICollection<ServiceOption> ServiceOptions { get; set; }
}
[Bind(Include="ServiceOptionID, Description, Tags")]
public class ServiceOption {
[Key]
public int ServiceOptionID { get; set; }
public int ServiceID { get; set; } /* parent id reference */
public string Description { get; set; }
public virtual ICollection<Inventory> InventoryItems { get; set; }
}
[Bind(Include = "InventoryID, Description")]
public class Inventory {
[Key]
public int InventoryID { get; set; }
public int ServiceOptionID { get; set; } /* parent id reference */
public string Description { get; set; }
}
Ideal Controller Method:
[HttpPost]
public ActionResult EditService(int id) {
Service service = db.Services.Single(s => s.ServiceID == id);
TryUpdateModel(service); // automatically updates child and grandchild records
if (ModelState.IsValid) {
db.SaveChanges();
return RedirectToAction("Index");
}
return View(service);
}
Is there a way to achieve this utopian dream, or am I barking up the wrong tree? I'm open to using another technology (such as normal EF4, Automapper etc)
Thanks in advance!
With just the default model binder? Probably not.
With a custom one? Probably.
However your issue won't be the model binder itself. Your issue will be that EF and ORMs and ( I think ) in general do not consider removing an item from a collection as a delete operation. In effect what you are telling the ORM is the relationship does not exist, not that a child row needs to be deleted. Depending on your mappings you'll usually get an error like "A referential integrity constraint violation occurred". This won't be because of code first this is just how EF works.
EF works this way by design and is really important for more complex relationships such as when you have m2m relationships which reference other m2m relationships. You really want EF to be able to disambiguate calls for removal of a relationship and calls to remove a row entirely.
Also, IMHO, this technique is also bad because your letting the piece of code responsible for mapping http values also dictate how objects should be persisted. This is a bad move. I consider delete operations a pretty sacrosanct act and shouldn't be left to the ModelBinder alone. Without soft deletes or logging deleting objects should be considered "serious business".
I am using EF4 code first and want to generate a composite key which is made of a class property and foreign key. I have two classes: Order and Company. The Order class holds a reference but this will not necessarily be unique between companies. So I intend to use a composite key made up of Reference and Company.CompanyId.
I have tried using the following to set it but I get an error message "Key expression is not valid".
modelBuilder.Entity<Order>().HasKey(o => new { o.Reference, o.Company.CompanyId });
I have also tried
modelBuilder.Entity<Order>().HasKey(o => new { o.Reference, o.Company });
and this fails.
these are my classes:
public class Order
{
public string Reference { get; set; }
public Company Company { get; set; }
}
public class Company
{
public int CompanyId { get; set; }
public virtual ICollection Orders { get; set; }
}
Any help would be greatly appreciated.
As Antony Highsky mentioned, you can only use scalar properties in the key.
So, you will need to add a foreign key (scalar property) to the Order class and associate it with the navigation property Company as shown below:
public class Order
{
public string Reference { get; set; }
public int CompanyId { get; set; }
[RelatedTo(ForeignKey = "CompanyId")]
public Company Company { get; set; }
}
And then create the composite key using the model builder:
modelBuilder.Entity<Order>().HasKey(o => new { o.Reference, o.CompanyId });
Note that data annotations (RelatedTo attribute) were introduced with the Entity Framework CTP 3. For another option that only uses data annotations instead of HasKey method, see this post:
http://www.luisrocha.net/2010/11/creating-composite-keys-using-code.html
One thing that doesn't look quite right is your use of the non-generic version of ICollection. Try this:
public virtual ICollection<Order> Orders { get; set; }
Did you try this?
modelBuilder.Entity().HasKey(o =>o.Reference );
modelBuilder.Entity().HasKey(o =>o.CompanyId );
According to this source, only scalar properties are allowed in the key. Navigation properties are not.