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.
Related
I have two sets of objects: Coupon and DiscountScheme.
Each have a connected object of {Type}Action, and identical configurations.
When making a request for Coupon, I do not get anything back,
but the same query for DiscountScheme works as expected
A condensed version of the classes (The full code and sql for the tables can be found here):
public class CouponAction
{
public int Id { get; set; }
public virtual Coupon Coupon { get; set; }
}
public class Coupon
{
public int Id { get; set; }
public virtual CouponAction Action { get; set; }
}
public class DiscountSchemeAction
{
public int Id { get; set; }
public virtual DiscountScheme DiscountScheme { get; set; }
}
public class DiscountScheme
{
public int Id { get; set; }
public virtual DiscountSchemeAction Action { get; set; }
}
The configuration:
public class CouponActionMap : EntityTypeConfiguration<CouponAction>
{
public CouponActionMap()
{
ToTable("CouponAction");
}
}
public class CouponMap : EntityTypeConfiguration<Coupon>
{
public CouponMap()
{
ToTable("Coupon");
HasRequired(c => c.Action);
}
}
public class DiscountSchemeActionMap : EntityTypeConfiguration<DiscountSchemeAction>
{
public DiscountSchemeActionMap()
{
ToTable("DiscountSchemeAction");
}
}
public class DiscountSchemeMap : EntityTypeConfiguration<DiscountScheme>
{
public DiscountSchemeMap()
{
ToTable("DiscountScheme");
HasRequired(ds => ds.Action);
}
}
The query I am trying to make:
using(var context = new Context()/* My database context, using a custom wrapper framework*/)
{
Console.WriteLine(context.Coupons.ToList()); // nothing
Console.WriteLine(context.DiscountSchemes.ToList()); // the contents of the table
}
If I query the actions table, I do get the contents, but again for CouponAction I do not get the connected Coupon, and for DiscountScheme it works as expected.
The issue is with your 1-to-1 relationship. By default EF expects a 1-to-1 to be using the PKs on both tables. By putting a CouponID on your CouponAction you are not setting a 1-to-1 relationship, you are setting a 1-to-many/many-to-1. Nothing stops several CouponAction records from having the same CouponId. You could put a unique constraint on CouponID, but if that were the case then you may as well have the CouponAction's PK to be the CouponID. Hence, this is why I don't advise using "Id" as a PK name, but rather CouponId vs. DiscountId, etc.
If the relationship between coupon and action is truly 1-to-1 then get rid of the CouponId on the Action table, and ensure you're using the same ID value across both tables for the related records. You can test this by changing your mapping to configure EF to use CouponId on the CouponAction as it's PK. Once you do that, you should see your related records coming up.
Alternatively you can establish a many to 1 relationship (HasOne.WithMany()) from Action to Coupon, but no return reference without a CouponActionId on Coupon. Or you can set up a 1-to-many where Coupon contains an ICollection<CouponAction> CouponActions even though you intend to only have one action per coupon. But if it is 1-to-1 then I would highly recommend using the same PK value across both tables.
How would you delete a relationship assuming you had the 2 entities, but did not have the 'relationship' entity?
Assuming the following entities...
Model classes:
public class DisplayGroup
{
[Key]
public int GroupId { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public ICollection<LookUpGroupItem> LookUpGroupItems { get; set; }
}
public class DisplayItem
{
[Key]
public int ItemId { get; set; }
public string Description { get; set; }
public string FileType { get; set; }
public string FileName { get; set; }
public ICollection<LookUpGroupItem> LookUpGroupItems { get; set; }
}
public class LookUpGroupItem
{
public int ItemId { get; set; }
public DisplayItem DisplayItem { get; set; }
public int GroupId { get; set; }
public DisplayGroup DisplayGroup { get; set; }
}
Here is the code for deleting a relationship. Note: I do not want to delete the entities, they just no longer share a relationship.
public void RemoveLink(DisplayGroup g, DisplayItem d)
{
_dataContext.Remove(g.LookUpGroupItems.Single(x => x.ItemId == d.ItemId));
}
The method above causes an error:
System.ArgumentNullException occurred
Message=Value cannot be null.
It looks like this is the case because LookUpGroupItems is null, but these were called from the database. I would agree that I do not want to load all entity relationship objects whenever I do a Get from the database, but then, what is the most efficient way to do this?
Additional NOTE: this question is not about an argument null exception. It explicitly states how to delete a relationship in Entity Framework Core.
The following is not the most efficient, but is the most reliable way:
public void RemoveLink(DisplayGroup g, DisplayItem d)
{
var link = _dataContext.Find<LookUpGroupItem>(g.GroupId, d.ItemId); // or (d.ItemId, g.GroupId) depending of how the composite PK is defined
if (link != null)
_dataContext.Remove(link);
}
It's simple and straightforward. Find method is used to locate the entity in the local cache or load it the from the database. If found, the Remove method is used to mark it for deletion (which will be applied when you call SaveChanges).
It's not the most efficient because of the database roundtrip when the entity is not contained in the local cache.
The most efficient is to use "stub" entity (with only FK properties populated):
var link = new LookUpGroupItem { GroupId = g.GroupId, ItemId = d.ItemId };
_dataContext.Remove(link);
This will only issue DELETE SQL command when ApplyChanges is called. However it has the following drawbacks:
(1) If _dataContext already contains (is tracking) a LookUpGroupItem entity with the same PK, the Remove call will throw InvalidOperationException saying something like "The instance of entity type 'LookUpGroupItem' cannot be tracked because another instance with the key value 'GroupId:1, ItemId:1' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
(2) If database table does not contain a record with the specified composite PK, the SaveChanges will throw DbUpdateConcurrencyException saying "Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions." (this behavior is actually considered a bug by many people including me, but this is how it is).
Shorty, you can use the optimized method only if you use short lived newly create DbContext just for that operation and you are absolutely sure the record with such PK exists in the database. In all other cases (and in general) you should use the first method.
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();
I need to insert an entity (Picture) that holds a related entity (Ad) based on TPH architecture:
Picture model:
public class Picture
{
// Primary properties
public int Id { get; set; }
public string Name { get; set; }
}
public class AdPicture : Picture
{
public Ad Ad { get; set; }
}
Ad model:
public class Ad
{
// Primary properties
public int Id { get; set; }
public string Title { get; set; }
}
public class AdCar : Ad
{
public int? CubicCapacity { get; set; }
public int? Power { get; set; }
}
I want to insert a new Picture in the AdCar, and I tried:
AdPicture picture = new AdPicture()
{
Ad = _adRepository.GetById(adId),
Filename = newFileName
};
_pictureService.CreateAdPicture(picture);
CreateAdPicture:
public void CreateAdPicture(AdPicture adPicture)
{
_adPictureRepository.Add(adPicture);
_adPictureRepository.Save();
}
But Entity Framework says
*Invalid column name 'AdCar_Id'.*
When I check the SQL command text, I can see
insert [dbo].[Pictures]([Name], [Filename], [URL], [Rank], [Discriminator], [PictureType_Id], [AdCar_Id], [Ad_Id])
values (null, #0, null, #1, #2, #3, #4, null)
It's putting AdCar_Id and Ad_Id, why? How can I insert the Picture related with the AdCar?
First up: You may need to define the key for the subclass explicitly to be Ad_Id as it is assuming the AdCar_Id (or vice-versa).
Something like this in your DbContext:
modelBuilder.Entity<AdPicture>()
.HasRequired(o => o.Ad)
.WithMany().Map(f => f.MapKey("Ad_Id"));
Second: Also, just to check - all of these classes have completely separate tables (TPH) and not tables with just the additional properties specified (TPH) I.e. an AdPicture does not have data in both a Picture and AdPicture table?
Having not done TPH (only TPT) I am unsure of if Entity Framework handles the subclass-ing differently, but I suspect it must. Someone else can hopefully answer that better if you are missing something there.
I found the problem:
I was defining the Navigation property "public IList Pictures { get; set; }" in the Ad Model that was creating an aditional field in the SQL Command "Ad_Id".
As soon as I removed it, the problem was solved and all the theory I had about TPH came togheter again :)
Thanks for your feedback.
I am new to asp.net C# and trying to learn by building a simple web app based on MVC 3 Music application. So far i have had a decent run but i am running into this this and i am not able to figure out the root cause. plz help
I am building a simple website where Projects are listed, then clicking on projects you see all the tables and then clicking on table you see all the columns. Projects/Tables/Column are being fetched from SQL db which has valid data and PK/FK keys defined. i am able to navigate from projects to tables and can see all columns under tables but when I click on column link, i get error as described below.
ERROR: "Invalid column name 'Tables_Id'." SQL profiler shows this column in the query but i do not understand where is it coming from as I do not have any such columm.
CONTROLLER CLASS
public class ProjectController : Controller // Inherit from base class Controller
{
DbEntities storeDB = new DbEntities(); //Create Object/instance of class //StorDB is reference to an object
public ActionResult Index()
{
var Name = storeDB.ProjectNM.ToList(); //Use 'var' coz we may have any type returned, 'var' is determined at run time
return View(Name);
}
public ActionResult BrowseTables(string Projects)
{
var ProjectModel = storeDB.ProjectNM.Include ("Tabless")
.Single(g => g.Name == Projects);
return View(ProjectModel);
}
public ActionResult BrowseColumns(string TableIs)
{
var ProjectModel1 = storeDB.TableNM.Include("Columnss")
.Single(g => g.Tbl_Name == TableIs);
return View(ProjectModel1);
//var ColumnModel = storeDB.TableNM.Find(TableIs);
// return View(ColumnModel);
}
}
Other Classes
public partial class Projects //Partial class, see comment below
{
public int Id { get; set; }
public string Name { get; set; }
public List<Tables> Tabless { get; set; } //Navigation Property, required so that we can include tables under projects
}
public class Tables
{
public int Id { get; set; }
public int ProjectId { get; set; }
public string Tbl_Name { get; set; }
public Projects Project { get; set; } //Class table can have (belong) only one project
public List<Columns> Columnss { get; set; } //Table can have more than one column
}
public class Columns
{
public int Id { get; set; }
[ForeignKey("Tables")]
public int TblId { get; set; }
public string Column_Name { get; set; }
public string IncludeFlag { get; set; }
}
View
<ul>
#foreach (var Tables in Model.Tabless)
{
<li>
#Html.ActionLink(Tables.Tbl_Name, "BrowseColumns", new { TableIs = Tables.Tbl_Name })
</li>
}
Query from SQL profiler
[Project2].[Tables_Id] AS [Tables_Id]
As you can seethe query has a column [Tables_Id] and I do nto understand why it is there as i do nto have any such column. Please help!
Basically MVC3 and EF4 do a lot of things on convention.
My suggestion to make things a little clearer for yourself is read up on EF 4.1 a little, and let it pluralize your table names for you, and use the data annotations (or property mapping if you don't like the attributes in your model) to mark your object's Id properties...
This is not necessarily the cause of your problem, but I think you will find it a lot easier to see what is going on in your profiler and models when the names/values make more logical sense.
Start by singularizing your objects: Table, Column, etc. or even using a more descriptive name... again if for no other reason it will be easier for you to read and debug, or even to get better answers here.
The convention for EF4.1 and foreign keys is to name them {TableName}_{ColumnName} so your foreign key to Tables is expecting a column names Table_Id (because Table is the name of the table, and Id is the name of the PK column.
This might help: EF 4.1 messing things up. Has FK naming strategy changed?