I'm running into an issue when inserting entities with one-to-many relationships into an SQLite database using Entity Framework 6. Let's consider this example:
Model
public class Box {
public int BoxId { get; set; }
public ICollection<Item> Items{ get; set; }
}
public class Item {
public int ItemId { get; set; }
public Box Box { get; set; }
}
Insertion
Box box = new Box(){BoxId = 1};
Item item = new Item(){ItemId = 1, Box = box};
using (var db = new MyDbContext()) {
db.Boxes.Add(box);
db.SaveChanges();
db.Items.Add(item);
db.SaveChanges();
}
This snippet works.
Box box = new Box(){BoxId = 1};
Item item = new Item(){ItemId = 1, Box = box};
using (var db = new MyDbContext()) {
db.Boxes.Add(box);
db.SaveChanges();
}
using (var db = new MyDbContext()) {
db.Items.Add(item);
db.SaveChanges();
}
This snippet doesn't work, throws an Exception with message UNIQUE constraint failed
I understand why it doesn't work, but I can't figure out a way to make it work. Using the same context is not an option, because the application could be restarted between the creation of the Box and the addition of an Item. I found an answer to the same question but using EF core instead of EF6 (right there (Stack Overflow)) but I can't access the TrackGraph property of the ChangeTracker with EF6.
I would also need to have a fully generic way of solving this issue, because I could possibly have other objects in my Boxes than Items.
Okay so I figured a way.
Before calling db.SaveChanges() I execute this code, where entities is the list of entities I just added to the DbContext. This way only entities that I explicitly wanted to add are added, other related entities are not added, and thus I don't run into Unique constraint failed because DbContext is not recreating related entities.
db.ChangeTracker.Entries().ToList().ForEach(dbe => {
if (!entities.Contains(dbe.Entity)) {
dbe.State = EntityState.Unchanged;
}
});
Related
Well, I have this configuration:
Item has 0 or many Groups and 0 or many Users (tables GroupsItem and UsersItem)
Groups and Users are inside 0 or many Items
Groups and Users are independently being created in the application
Here's the problem: When I try to insert a new Item I have to point out what are its Groups and Users (which already exists). When it happens, the tables GroupsItem, UsersItem and Item are being correctly populated, but I'm having duplicated registers at Groups and Users.
Here is my code summarized:
Item:
public class Item {
public ICollection<Groups> GROUPS{ get; set; }
public ICollection<Users> USERS{ get; set; }
}
Groups: (Users have the same structure)
public class Groups{
public ICollection<Item> ITEM { get; set; }
}
Inserting a new Item:
public static void InsertingItem(){
Item example = new Item(){
GROUPS = AlreadyExistingGroup()
}
using (myDbContext db = new myDbContext()){
db.ITEMS.Add(example);
db.SaveChanges();
}
}
And that's it. AlreadyExistingGroup is a method that returns a List<Groups> which is populated with groups that already exist in the database, the method that brings these groups is a single function that brings one single group but it's called multiple times:
public static Groups FetchGroups(int id) {
try {
using (myDbContext db = new myDbContext ()) {
Groups group = db.GROUPS.Where(x => x.CODGROUP == id).FirstOrDefault();
return group;
}
} catch (Exception e) {
return null;
}
}
What am I doing wrong that is causing duplicate registers at Groups and Users?
Editing my answer with the correct solution we came to in comments:
The issue lies with the two different DbContexts in the code:
public static void InsertingItem(){
Item example = new Item(){
// DbContext #1 is created in this method
GROUPS = AlreadyExistingGroup();
}
// And this is DbContext #2
using (myDbContext db = new myDbContext()){
db.ITEMS.Add(example);
db.SaveChanges();
}
}
The fix is to use the same DbContext for both the lookup and the insert of a new item. Example:
public static void InsertingItem(){
using (myDbContext db = new myDbContext()){
Item example = new Item(){
// refactor the AlreadyExistingGroup method to accept a DbContext, or to move
// the code from the method here
GROUPS = AlreadyExistingGroup(dbContext) ;
}
db.ITEMS.Add(example);
db.SaveChanges();
}
}
If I'm understanding your setup correctly, I think you'd want Groups to only have one parent Item reference.
public class Groups{
public Item ITEM { get; set; } //
}
Also, and I'm not downvoting or criticizing, but just a suggestion: It's helpful to also post the model configuration as well when asking EF questions. Because... well... EF can be finicky. Aka:
modelBuilder.Entity<Group>()
.HasMaxLength(50)
.WhateverElseYouConfigure();
Based on your clarification in the comments it seems you are using untracked (unattached) Group and User entities when setting them in your new Item entity. When the Item entity is added to the Items DbSet, it is tracked as EntityState.Added. EF will propagate the Item entity's object graph and as it comes across untracked related entities (i.e. the User and Group collections you've set), it will track the formerly untracked entities as EntityState.Added as well, thus inserting new records in the data store for those entities.
To solve the problem, manually attach these related entities as EntityState.Unchanged. As an example, you can use the DbContext.AttachRange method
db.AttachRange( existingGroups );
Or per entity you can also attach via the DbContext.Entry method
db.Entry( existingGroup ); // this tracks the entity as Unchanged
I'm trying to update a one-to-many relationship with EntityFramework, but EF won't save the relationship for some reason. I'm using ASP.Net MVC, but that does not seem to matter in this case as the data is received correctly.
I've tried a lot of possible solutions and some tutorials, unfortunately almost all of them describe a scenario where the connection is made via a foreign key property in the class itself.(I'm aware that EF adds a FK in the database, but i cant access that directly.) My approach seems to be significantly different as none of their solution seems to work for me.
The code below seems to me to be the most promising, but it still doesn't work. the foreign key of the activity object doesn't get updated.
Removing context.Entry(act.ActivityGroup).State = EntityState.Detached; causes a Primary Key collision, as EF tries to insert the ActivityGroup as a new Entity. Marking it as Modified, doesn't do the trick either.
Models:
public class Activity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid ActivityID { get; set; }
public string Name { get; set; }
public ActivityGroup ActivityGroup { get; set; }
}
public class ActivityGroup
{
public int ActivityGroupID { get; set; }
public string GroupName { get; set; }
public string BackgroundColor { get; set; }
}
Method to save Data
public ActionResult SaveActivities(List<Activity> activities)
{
if (ModelState.IsValid)
{
using (TSSDBContext context = new TSSDBContext())
{
foreach (Activity act in activities)
{
if (act.ActivityGroup != null)
{
context.Entry(act.ActivityGroup).State = EntityState.Detached;
}
context.Entry(act).State = (act.ActivityID == null || act.ActivityID == Guid.Empty) ? EntityState.Added : EntityState.Modified;
}
context.SaveChanges();
return new HttpStatusCodeResult(200);
}
}else
{
return new HttpStatusCodeResult(500);
}
}
You could try something like this.
EF context is tracking each entity you don't need manually marking
entities , Modified or Added for each. Read about Entityframework context tracking
Just fetch the entities what you need and decide to insert or update on based on your condition and just Add what should be added and update
Just do a SaveChanges EF will show the magic
This is a basic idea of inserting and updating entities at one shot. If you have concerns about performance i would suggest you to update using AddRange method in EF 6.0
using(var db1 = new Entities1())
{
var activitylists = db.Activity.ToList();
foreach (var item in activitylists )
{
if(item.Id==null)
{
var newActivity= new Activity();
//Your entities
newActivity.Name="Name";
db.Activity.Add(newActivity);
db.Entry<Activity>(item).State = System.Data.Entity.EntityState.Added;
}
else
{
item.Name="new name update";
db.Entry<Activity>(item).State = System.Data.Entity.EntityState.Modified;
}
}
db.SaveChanges();
}
Update : if your getting data from PostRequest , you need to manually mark the entities as modified or added as the context is not aware of what to do with entities
I am working in a small project using Web API, and entity Framework. And I facing some issue in posting my entity.
My entities look like this:
public class DayExercises
{
public DayExercises()
{
Exercises = new List<Exercise>();
}
[Key]
public int Id { get; set; }
public string Day { get; set; }
public virtual ICollection<Exercise> Exercises { get; set; }
}
and my Exercise entity look like this.
public class Exercise
{
public Exercise()
{
DayExercises = new List<DayExercises>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual List<DayExercises> DayExercises { get; set; }
}
and my web api method for posting the dayExercises look like this
[ResponseType(typeof(WorkoutTemplate))]
public IHttpActionResult PostWorkoutTemplate(DayExercises dayExercises)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
foreach (var dayExercise in dayExercises)
{
fitnessDbContext.Entry(dayExercise).State = EntityState.Added;
foreach (var exercise in dayExercise.Exercises.ToList())
{
fitnessDbContext.Entry(exercise).State = EntityState.Unchanged;
}
}
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = dayExercises.Id }, dayExercises);
}
The relationship is Many-To-Many.
Problem:
I am sending a dayExercise with existing Exercise(already existing in database) to my method. but when I'm posting dayExercise with SAME two exercises. it throw that exception:
Additional information: Saving or accepting changes failed because more than one entity of type 'FitnessFirst.WebApi.Exercise' have the same primary key value.
Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model.
Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
I also tried to Deattach the entities, and Get the exercise from Database using their ID and Attach them again and add it to the dayExercise but it doesn't save to database.
NOTE: when I add two different exercises, it doesn't throw that Exception.
I also read the following answers, but it doesn't solve it: Ensure that explicitly set primary key values are unique
Any Suggestion or explanation.
I know this is an old post, but I ran into the same issue yesterday. This is the solution I came up with. Basically entity framework change tracker only allows unique values for an entity. So to get around the error you need to check if the entity already exists in the change tracker and use that instance.
var excercises = dayExercise.Exercises.ToList();
for (int i = 0; i < excercises.Count; i++)
{
var unchangedEntity = _dbContext.ChangeTracker.Entries<Exercise>()
.Where(xy => xy.State == EntityState.Unchanged &&
xy.Entity.Id == excercises [i].Id).FirstOrDefault();
if (unchangedEntity == null)
{
fitnessDbContext.Entry(excercises[i]).State = EntityState.Unchanged;
}
else
{
excercises[i] = unchangedEntity.Entity;
}
}
Suppose I have the following model classes in an Entity Framework Code-First setup:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
The database created from this code includes a TeamPersons table, representing the many-to-many relationship between people and teams.
Now suppose I have a disconnected Person object (not a proxy, and not yet attached to a context) whose Teams collection contains one or more disconnected Team objects, all of which represent Teams already in the database. An object such as would be created by the following, just for example, if a Person with Id 1 and a Team with Id 3 already existed in the db:
var person = new Person
{
Id = 1,
Name = "Bob",
Teams = new HashSet<Team>
{
new Team { Id = 3, Name = "C Team"}
}
};
What is the best way of updating this object, so that after the update the TeamPersons table contains a single row for Bob, linking him to C Team ? I've tried the obvious:
using (var context = new TestContext())
{
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
but the Teams collection is just ignored by this. I've also tried various other things, but nothing seems to do exactly what I'm after here. Thanks for any help.
EDIT:
So I get that I could fetch both the Person and the Team[s] from the db, update them and then commit changes:
using (var context = new TestContext())
{
var dbPerson = context.People.Find(person.Id);
dbPerson.Name = person.Name;
dbPerson.Teams.Clear();
foreach (var id in person.Teams.Select(x => x.Id))
{
var team = context.Teams.Find(id);
dbPerson.Teams.Add(team);
}
context.SaveChanges();
}
This is a pain if Person's a complicated entity, though. I know I could use Automapper or something to make things a bit easier, but still it seems a shame if there's no way of saving the original person object, rather than having to get a new one and copy all the properties over...
The general approach is to fetch the Team from the database and Add that to the Person's Teams collection. Setting EntityState.Modified only affects scalar properties, not navigation properties.
Try selecting the existing entities first, then attaching the team to the person object's team collection.
Something like this: (syntax might not be exactly correct)
using (var context = new TestContext())
{
var person = context.Persons.Where(f => f.Id == 1).FirstOrDefault();
var team = context.Teams.Where(f => f.Id == 3).FirstOrDefault();
person.Teams.Add(team);
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
That's where EF s**ks. very inefficient for disconnected scenario. loading data for the update/delete and every for re-attaching updated, one cannot just attached the updated entity to the context as an entity with the same key might already existed in the context already, in which case, EF will just throw up. what need to be done is to check if an entity with the same key is already in the context and attached or updated accordingly. it's worse to update entity with many to many relationship child. removing deleted child is from the child's entity set but not the reference property, it's very messy.
You can use the Attach method. Try this:
using (var context = new TestContext())
{
context.People.Attach(person);
//i'm not sure if this foreach is necessary, you can try without it to see if it works
foreach (var team in person.Teams)
{
context.Teams.Attach(team);
}
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
I didn't test this code, let me know if you have any problems
I'm trying to update an object that I have previously saved with EntityFramework 4.1 (CodeFirst)
The class Job has the following properties ...
public class Job
{
[key]
public int Id { get; set; }
public string Title { get; set; }
public Project Project { get; set; }
public JobType JobType { get; set; }
public string Description { get; set; }
}
The initial create works fine, but the update only commits changes to the strings..
If I change the child objects eg the JobType Property from JobTypeA to JobTypeB - the change is not committed ...
I'm not looking to commit a change to JobType - only to Job.
using (var context = new JobContext())
{
context.Jobs.Attach(job);
context.Entry(job).State = EntityState.Modified;
context.SaveChanges();
}
Having a look at SQL Profiler - the Ids are not even being sent for the Update - however they are for the initial insert!
Setting the state to Modified only updates scalar and complex properties, not your navigation properties. This only goes through Entity Framework's change detection. It means that you need to load the original from the database:
using (var context = new JobContext())
{
var originalJob = context.Jobs.Include(j => j.JobType)
.Single(j => j.Id == job.Id);
// Update scalar/complex properties
context.Entry(originalJob).CurrentValues.SetValues(job);
// Update reference
originalJob.JobType = job.JobType;
context.SaveChanges();
}
You could probably also leverage some "tricks" in your case:
using (var context = new JobContext())
{
var jobType = job.JobType;
job.JobType = null;
context.JobTypes.Attach(jobType);
context.Jobs.Attach(job);
// change detection starts from here,
// EF "thinks" now, original is JobType==null
job.JobType = jobType;
// change detection will recognize this as a change
// and send an UPDATE to the DB
context.Entry(job).State = EntityState.Modified; // for scalar/complex props
context.SaveChanges();
}
It wouldn't work though if you want to set JobType to null.
This is a typical situation which is getting much simpler if you expose foreign keys as properties in your model: With a JobTypeId in your Job entity your code would work because the FK property is scalar and setting the state to Modified will also mark this property as modified.