I can not find the cause why I am failing to insert object graph
I have an object graph - Promotion with 1..N Flow records.
When I create a new Promotion record, I need to create a related flow record as well.
This is how I try to do it
var newpromo = new Promotion();
var newflow = new Flow();
newpromo.Flow.Add(newflow);
//i thought this should enough to tell EF that newflow's PromotionId
//should be the newly inserted nepromo's Id
newflow.Promotion = newpromo;
//...
db.Promotions.Attach(newpromo);
db.Entry(newflow).State = System.Data.Entity.EntityState.Added;
but when i call db.SaveChanges() I receive this error
{"The INSERT statement conflicted with the FOREIGN KEY constraint
\"FK_PromotionFlow_Promotions\". The conflict occurred in database \"RepositoryDb\",
table\"dbo.Promotions\", column 'Id'.\r\nThe statement has been terminated."}
What could be the cause of the problem?
DEFINITIONS:
1) This is Promotion and Flow POCO class definitions
class Promotion
{
public Int32 Id { get; set; }
public List<PromotionFlow> Flow { get; set; }
//...other fields
}
class PromotionFlow
{
public Int32 Id { get; set; }
public Int32 PromotionId { get; set; }
public Promotion Promotion { get; set; }
//other fields
}
2) I have set the mapping using fluent api
class PromotionMapping : EntityTypeConfiguration<Promotion>
{
public PromotionMapping()
{
//Mapping both tables
HasMany(x => x.Flow).WithRequired(x=>x.Promotion).HasForeignKey(x => x.PromotionId);
//...other mapping
}
}
3) In the database I have configured PromotionFlow.PromotionId column as a foreign key to Promotion.Id column
CONSTRAINT [FK_PromotionFlow_Promotions] FOREIGN KEY ([PromotionId]) REFERENCES [dbo].[Promotions] ([Id])
Try saving the promo first, then adding the flow to the promo and saving the flow (or the promo).
It sounds like your promo doesn't have an ID to use for the flow because the promo has not yet been inserted into the DB.
I found the cause of the problem... I needed to set object graph parent's (that is Promotion object) entry's state to "Added" (attaching to context was not enough since this was a NEW object)
//(...)
db.Promotions.Attach(newpromo);
I was missing THIS row after attaching new Promotion object to context!
db.Entry(newpromotion).State = System.Data.Entity.EntityState.Added; //<==THIS ROW
//... dealing with the child (flow) entry states
db.SaveChanges();
P.S. the reason I was not using DbContext.DbSet.Add() method was because it sets entity state to Added for all of the objects in the graph (and some of my objects were referencing "Settings" and "Category" type data, that I was not intended to insert once more)
db.Promotions.Add(newpromo);
P.S. Now it is inserting all of the graph in one db.SaveChanges() call, no need to insert Promotion before inserting Flow objects
Related
I have a problem with populating the right values in a junction table for a many to many relationship. In the image below I have simplified what I am trying to do. The "Table on the left" has values in the database that I want to use. The table of the right is about to receive new records. It has a navigation property to the Junction table and the same is true for the table on the left. The junction table has navigation properties to both tables on its rear and they are set to required.
When I create the new records in the table on the right, I also add to it records in the junction table. The TOL_ID is known as it is saved in the database, but the TOR_ID is about to be created, therefore unknown. When I attempt to call the SaveChanges in my context, it tries to save the junction table records first, before the TOR_IDs have been populated for the record on the right. I though that marking the navigation property as Required would make the EF understand that TOR_ID must exist before creating the junction table row. Instead it tries to insert an existing TOL_ID and 0, which gives a violation when trying to insert many table on the right records connected to the same TOL_ID.
Note: Saving the TOR_IDs first and then connecting with junction records is not an option, as the creation of the junction table records are part of a "Slowly changing dimension" type 6 flow.
This is how it really looks like in the code:
// The newRating is the new object corresponding the Table on the right
var newRating = new ModuleRating()
{
// The moduleRating.RatedDriveUnit already exists in the db
RatedDriveUnit = moduleRating.RatedDriveUnit
};
newModule.Ratings.Add(newRating);
If you follow the Code First below classes will helpful
public class TOL
{
[Key]
public int TOL_ID { get; set; }
public int Col1 { get; set; }
public ICollection<TOR> Tors { get; set; }
}
public class TOR
{
[Key]
public int TOR_ID { get; set; }
public int Col1 { get; set; }
public ICollection<TOL> Tols { get; set; }
}
public class TolTorContext : DbContext
{
public DbSet<TOL> Tols { get; set; }
public DbSet<TOR> Tors { get; set; }
}
If you follow database first approach,make FK at Join Table and try to chhange id names TOLId , TORId
I'am sorry for spending your time. After some investigation I noticed that the supposed composite unique index (TOL_ID and TOR_ID) of the junction table had been set wrong. Instead two separate unique indexes had been applied to TOL_ID and TOR_ID, which resulted a constrain violation as soon as one of those two values appeared twice.
I use Entity Framework as ORM in my project. Let's suppose I use Code-First pattern and I have two models. Such as
internal class First
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
}
internal class Second
{
public int Id { get; set; }
public First ForeignKeyEntity { get; set; }
// other members
}
And here is code populating database:
List<Second> res = GetData();
using (var ctx = new StatisticContext())
{
foreach (var item in res)
{
ctx.Seconds.Add(item);
}
ctx.SaveChanges();
}
As you can see each instance of class Second has instance of class First in its member ForeignKeyEntity. Obviously some instances of First can be duplicated in res. And when I run this code I get DbUpdateException in ctx.SaveChanges() with inner exception that has the following message:
Violation of PRIMARY KEY constraint 'PK_dbo.First'.
Cannot insert duplicate key in object 'dbo.First'.
The duplicate key value is (29459). The statement has been terminated.
I can not to insert duplicated rows but I don't want to insert duplicates, I would like to insert row only if it doesn't exist. How to solve this problem? How to insert foreign key object only if doesn'tt exist?
The normal way of doing things would be to do a read first with item to see if it exists. If it does then you need to use ctx.Seconds.Update(item);
If your items are already on the context, then you can check the state. it will be either State.Modified or State.Added.
Whats in GetData()
I use Oracle EF 5.0 provider. Oracle 11G database.
Here is my database schema:
Table has an Id as primary key. For each table in database there are trigger, that fires on insert new record and EF get primary key after insert from sequence. Sequences are created for each table.
In my edmx file each ID column has an StoreGeneratedPattern="Identity" attribute set.
My code is:
using (var context = new Entities())
{
var title = new TITLE
{
TITLE_NUMBER = 4000001,
IS_DRAFT = "N",
REGISTRY_DATE = DateTime.Now
};
var titleName = new TITLE_NAME
{
NAME = "title name"
};
title.TITLE_NAME.Add(titleName);
context.Set<TITLE>().Add(title);
context.SaveChanges();
}
When context.SaveChanges() are executed, there is exception thrown:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
But changes are saved in database correctly. And my Title object has correct Id for Title and for Title_Name. What can I do? Or what I do wrong?
Opps. I remove inheritance from edmx and error goes away!....
But all project is build in inheritance!!!
Ops. MS SQL with same scheme gives this exception too.
From here: https://entityframework.codeplex.com/workitem/2515
EF Team Triage: This is because you have identity configured with a TPC mapping. Because the two types are mapped to completely separate tables, you will get duplicate IDs generated since the database doesn't know to generate unique values between the two tables. One way around this is to modify the tables to have one generate odd numbers and the other generate even numbers.
Workaround for this.
We create Interface with shared properties for all entities and remove inheritance from edmx file.
I.e.
public interface IHistoricalEntity
{
int ID { get; set; }
int ENTITY_ID { get; set; }
DateTime CREATE_DATE { get; set; }
DateTime DELETE_DATE { get; set; }
int CREATOR { get; set; }
}
in partial file TITLE.cs
public partial class TITLE : IHistoricalEntity
{
}
and thus we can use generic version for all entities in our project.
Most posts around the ObjectStateManager are true-duplicate issues based on unique primary keys. My problem is that my table does Not have a primary key, but it does have multiple foreign keys, one of which is Nullable.
class MyObject
{
int Key1;
int? Key2;
}
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = null; });
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = 2000; }); ****
It blows up on the second call, even though this is a unique row in the database.
Any thoughts on how to get around this? or enforce checking of BOTH keys?
As #BenAaronson mentioned, you should have a surrogate, primary key in your table in this instance. Entity Framework quite simply cannot deal with entities that have no primary key defined—in fact, I'm surprised your code even compiled/ran. Perhaps your real code with real class and property names caused EF to infer a primary key using its default conventions. For example:
public class MyClass
{
public int MyClassId { get; set; }
public int MyOtherClassId { get; set; }
}
In the code above, even without explicitly declaring it, EF would assume that the MyClassId property is the primary key for the class MyClass, even if that may not have been your intention.
If EF can't infer a primary key and one is not explicitly provided, then your code wouldn't compile (or at most, it wouldn't run).
So looking at your code, what appears to be happening is that EF inferred a primary key somehow (in your example above, Key1). You then tried to attach a new object to your context:
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = null; });
This results in the context adding a new MyObject instance whose primary key value is 100 and whose Key2 property is null.
Next, you attempt to attach another item to the context:
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = 2000; });
What this does is attempt to add a new item to the context whose primary key is 100, and this fails. This is because you already have an object being tracked by the context whose primary key value is 100 (executed by the first statement above).
Since you need to allow possibly null values for the Key2 property, you can't use a composite primary key, as you already stated. So you will need to follow #BenAaronson's advice and add a surrogate primary key:
public class Object
{
// Alternatively, you can use a mapping class to define the primary key
// I just wanted to make the example clear that this is the
// surrogate primary key property.
[Key]
private int ObjectID { get; set; } // IIRC, you can make this private...
public int Key1 { get; set; }
public int Key2 { get; set; }
}
Now, you can do the following:
context.MyTable.Add(new MyObject() { Key1 = 100, Key2 = null; });
context.MyTable.Add(new MyObject() { Key1 = 100, Key2 = 2000; });
Notice I used the Add method and not Attach. That's because when using Attach, the context is assuming that you're adding an object to the context which already exists in the database, but which was not brought into the context via a query; instead, you had a representation of it in memory, and at this point, you want the context to start tracking changes made to it and update the object in the database when you call context.SaveChanges(). When using the Attach property, the context adds the object in the Unmodified state. That's not what we want. We have brand new objects being added to the context. So we use Add. This tells the context to add the item in the Added state. You can make any changes you want to it. Since it's a new item, it will be in the Added state until you call context.SaveChanges() and the item is persisted to your data store, at which time, it's state will be updated to Unmodified.
One more thing to note at this point. If this is a "many-to-many" table, you should never need to manually add rows to this type of join table in EF (there are some caveats to this statement, see below). Instead, you should setup a mapping between the two objects whose relationship is many-to-many. It's possible to specify an optional many-to-many relationship, too. If the first object has no relationship to the second, there should be no row in the join table for the first object, and vice versa.
Regarding join table caveats as alluded to above: if your join-tables (i.e. many-to-many mapping tables) are simple (meaning the only columns in the table are those columns mapping one ID to the related ID), then you won't even see the join-table as part of your object model. This table is managed by EF in the background through navigation properties on the related objects. However, if the join-table contains properties other than just the ID properties of the related objects (and, this implies you have an existing database or explicitly structured your object model this way), then you will have an intermediate entity reference. For example:
public class A
{
public int ID { get; set; }
}
public class B
{
public int ID { get; set; }
}
public class AToB
{
// Composite primary key
[Key]
public int IdA { get; set; }
[Key]
public int IdB { get; set; }
public A SideA { get; set; }
public B SideB { get; set; }
// An additional property in the many-to-many join table
public DateTime Created { get; set; }
}
You would also have some mappings to tell EF how to wire up the foreign key relationships. What you'd wind up with in your object model then, is the following:
myA.AToB.SideB // Accesses the related B item to this A item.
myA.AToB.Created // Accesses the created property of AToB, telling you
// when the relationship between A and B was created.
In fact, if you have non-trivial join tables such as this example, EF will always include them in your object model when generating its model from an existing database.
I would strongly suggest that you check out Julie Lerman's and Rowan Miller's books on programming Entity Framework.
I have a Business and a Category model.
Each Business has many Categories via an exposed collection (Category is disregarding the Business entity).
Now here is my controller-action:
[HttpPost]
[ValidateAntiForgeryToken]
private ActionResult Save(Business business)
{
//Context is a lazy-loaded property that returns a reference to the DbContext
//It's disposal is taken care of at the controller's Dispose override.
foreach (var category in business.Categories)
Context.Categories.Attach(category);
if (business.BusinessId > 0)
Context.Businesses.Attach(business);
else
Context.Businesses.Add(business);
Context.SaveChanges();
return RedirectToAction("Index");
}
Now there are several business.Categories that have their CategoryId set to an existing Category (the Title property of Category is missing tho).
After hitting SaveChanges and reloading the Business from server, the Categories are not there.
So my question is what's the proper way to set Business.Categories with a given array of existing CategoryIds.
When create a new Business however, the following DbUpdateException exception is thrown when calling SaveChanges:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
Inner exception (OptimisticConcurrencyException):
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
Update
after answer, here's the update code:
var storeBusiness = IncludeChildren().SingleOrDefault(b => b.BusinessId == business.BusinessId);
var entry = Context.Entry(storeBusiness);
entry.CurrentValues.SetValues(business);
//storeBusiness.Categories.Clear();
foreach (var category in business.Categories)
{
Context.Categories.Attach(category);
storeBusiness.Categories.Add(category);
}
When calling SaveChanges, I'm getting the following DbUpdateException:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
Here's how the Business/Category models look like:
public class Business
{
public int BusinessId { get; set; }
[Required]
[StringLength(64)]
[Display(Name = "Company name")]
public string CompanyName { get; set; }
public virtual BusinessType BusinessType { get; set; }
private ICollection<Category> _Categories;
public virtual ICollection<Category> Categories
{
get
{
return _Categories ?? (_Categories = new HashSet<Category>());
}
set
{
_Categories = value;
}
}
private ICollection<Branch> _Branches;
public virtual ICollection<Branch> Branches
{
get
{
return _Branches ?? (_Branches = new HashSet<Branch>());
}
set
{
_Branches = value;
}
}
}
public class Category
{
[Key]
public int CategoryId { get; set; }
[Unique]
[Required]
[MaxLength(32)]
public string Title { get; set; }
public string Description { get; set; }
public int? ParentCategoryId { get; set; }
[Display(Name = "Parent category")]
[ForeignKey("ParentCategoryId")]
public virtual Category Parent { get; set; }
private ICollection<Category> _Children;
public virtual ICollection<Category> Children
{
get
{
return _Children ?? (_Children = new HashSet<Category>());
}
set
{
_Children = value;
}
}
}
Just to make it clear again, the Category I'm attaching to existing/new Businesses already exist in the DB and have an ID, which is what I'm using to attach it with.
I treat the two cases - updating an existing business and adding a new business - separately because the two problems you mentioned have different reasons.
Updating an existing Business entity
That's the if case (if (business.BusinessId > 0)) in your example. It is clear that nothing happens here and no change will be stored to the database because you are just attaching the Category objects and the Business entity and then call SaveChanges. Attaching means that the entities are added to the context in state Unchanged and for entities that are in that state EF won't send any command to the database at all.
If you want to update a detached object graph - Business plus collection of Category entities in your case - you generally have the problem that a collection item could have been removed from the collection and an item could have been added - compared to the current state stored in the database. It might be also possible that a collection item's properties and that the parent entity Business have been modified. Unless you have tracked all changes manually while the object graph was detached - i.e. EF itself could not track the changes - which is difficult in a web application because you had to do this in the browser UI, your only chance to perform a correct UPDATE of the whole object graph is comparing it with the current state in the database and then put the objects into the correct state Added, Deleted and Modified (and perhaps Unchanged for some of them).
So, the procedure is to load the Business including its current Categories from the database and then merge the changes of the detached graph into the loaded (=attached) graph. It could look like this:
private ActionResult Save(Business business)
{
if (business.BusinessId > 0) // = business exists
{
var businessInDb = Context.Businesses
.Include(b => b.Categories)
.Single(b => b.BusinessId == business.BusinessId);
// Update parent properties (only the scalar properties)
Context.Entry(businessInDb).CurrentValues.SetValues(business);
// Delete relationship to category if the relationship exists in the DB
// but has been removed in the UI
foreach (var categoryInDb in businessInDb.Categories.ToList())
{
if (!business.Categories.Any(c =>
c.CategoryId == categoryInDb.CategoryId))
businessInDb.Categories.Remove(categoryInDb);
}
// Add relationship to category if the relationship doesn't exist
// in the DB but has been added in the UI
foreach (var category in business.Categories)
{
var categoryInDb = businessInDb.Categories.SingleOrDefault(c =>
c.CategoryId == category.CategoryId)
if (categoryInDb == null)
{
Context.Categories.Attach(category);
businessInDb.Categories.Add(category);
}
// no else case here because I assume that categories couldn't have
// have been modified in the UI, otherwise the else case would be:
// else
// Context.Entry(categoryInDb).CurrentValues.SetValues(category);
}
}
else
{
// see below
}
Context.SaveChanges();
return RedirectToAction("Index");
}
Adding a new Business entity
Your procedure to add a new Business together with its related Categories is correct. Just attach all Categories as existing entities to the context and then add the new Business to the context:
foreach (var category in business.Categories)
Context.Categories.Attach(category);
Context.Businesses.Add(business);
Context.SaveChanges();
If all Categories you are attaching really have a key value that exists in the database this should work without exception.
Your exception means that at least one of the Categories has an invalid key value (i.e. it does not exist in the database). Maybe it has been deleted in the meantime from the DB or because it is not correctly posted back from the Web UI.
In case of an independent association - that is an association without FK property BusinessId in Category - you get indeed this OptimisticConcurrencyException. (EF seems to assume here that the category has been deleted from the DB by another user.) In case of a foreign key association - that is an association which has a FK property BusinessId in Category - you would get an exception about a foreign key constraint violation.
If you want to avoid this exception - and if it occurs in fact because another user deleted a Category, not because the Category is empty/0 since it doesn't get posted back to the server (fix this with a hidden input field instead) - you better load the categories by CategoryId (Find) from the database instead of attaching them and if one doesn't exist anymore ignore it and remove it from the business.Categories collection (or redirect to an error page to inform the user or something like that).
I had this exception as well.
The problem with me was that the primary key of the added object was not being set by ADO Entity Framework. This resulted in the problem that the foreign key of the added object in the database could not be set as well.
I solved this problem by making sure the primary key was being set by the database itself.
If you're using SQL Server you can do that by adding the IDENTITY keyword with the column declaration in the CREATE TABLE statement.
Hope this helps
I was getting this exception on a field I was trying to set beyond its MaxLength attribute. The field also had a Required attribute. I'm working against an Oracle back-end database. It wasn't until I increased the length did I finally get a descriptive error message from the underlying database engine that the length was too long. I suspect the foreign key related error message vaguely means that not all relationships could be updated because one of the inserts failed and didn't get their Key updated.