How to check for duplicates before saving? - c#

I'm having a heck of a time figuring out how to add entities like this to my db.
public class ThingWithListings
{
public virtual ICollection<Listing> Listings;
}
public class Listing
{
public int Id;
public virtual ListingData Data { get; set; } // has a FK to ListingData
public DateTime Creation { get; set; }
}
public class ListingData
{
public int listing_id; // PK
....
}
I'm retrieving a 'ThingWithLIstings' from another source and writing it to my db. The tricky part is that any number of Listings may report to the same ListingData. So when I add or update a ThingWithListings, I need to see if a ListingData already exists and if so, just use that one.
I'm new to EF, so I've been using the AddOrUpdate from Author Vickers' article here: Obviously, this doesn't work for this scenario and so I've tried for a day or so to figure out the right way to do this. I'll spare you all the story of my main failed attempts and hope someone can just tell me the right way to do this.

if (DatabaseContext.ListingData.Any(l => l.listing_id == myId))
{
//already exists
}
else
{
//do whatever
}

var newArrivals = new ThingWithListings();
newArrivals.Listings = new List<Listing>();
newArrivals.Listings.Add(
new Listing()
{
creation = DateTime.Now,
ListingData = new ListingData()
{
listing_id = 1
}
});
//another Listing with the same ListingData listing_id
newArrivals.Listings.Add(
new Listing()
{
creation = DateTime.Now,
ListingData = new ListingData()
{
listing_id = 1
}
});
//dummy id generator
int counter = 1;
using (var ctx = new Database1Entities())
{
//get the ListingData from the current db context
var dbListingData = ctx.ListingData;
// the new arrivals
foreach (Listing item in newArrivals.Listings)
{
//get the listing_id of a new arrival's ListingData
int id = item.ListingData.listing_id;
//get the ListingData from the db, if it exists
var listingDataFromDb = dbListingData.FirstOrDefault(i => i.listing_id == id);
//if not, create it and add it to the db
if (listingDataFromDb == null)
{
listingDataFromDb = new ListingData()
{
//use the new arrival's listing_id
listing_id = item.ListingData.listing_id
};
ctx.ListingData.Add(listingDataFromDb);
ctx.SaveChanges();
}
//add the Listing by using the listingDataFromDb, which now references the db ListingData
ctx.Listing.Add(new Listing()
{
id = counter++,
creation = item.creation,
ListingData = listingDataFromDb
});
ctx.SaveChanges();
}
}

I assume that besides the Data object reference you also have the primitive foreign key field listing_id in your Listing class. If not, I recommend adding it.
You could start by fetching the existing listing_ids in a list or array. That saves numerous database round trips later.
Then the process is really simple: for each Listing object that arrives check whether its listing_id occurs in the pre-fetched list:
If so, do nothing with ListingData - just add (or update) the Listing, including the listing_id property.
If not, add the Listing and set Listing.Data with the ListingData object, both as new (Added) objects. EF will set the keys.
(Note that this assumes that there are no concurrent users modifying ListingData, so it is safe to take a snapshot of the Id's)

Related

How to save/update only parent entities without saving its childs entities in EF6 in asp.net mvc?

I am working on a survey application with Asp.Net MVC.
I have a page named Index.cshtml which has a question table and a 'Add New' button.Once button clicked, a popup is opened with jQuery. I am calling a view from controller to fill jQuery dialog named as AddOrEdit.cshtml (child page). I am adding new question and options. Question is a textfield and its options are added in editable table. Once clicked submit button, Submit form event (save or update) is fired. My Question and its Options class has a one-to-many relatonship. EF6 tries to save parent entities with its child entities. But I want to save childs after insertion of parents not the same time. How can I handle this problem.
I am using DB First approach. What is the best practice?
Question.cs
namespace MerinosSurvey.Models
{
using System;
using System.Collections.Generic;
public partial class Questions
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Questions()
{
this.Responses = new HashSet<Responses>();
this.Options = new HashSet<Options>();
}
public int QuestionId { get; set; }
public string QuestionName { get; set; }
public int QuestionTypeId { get; set; }
public System.DateTime CreatedDate { get; set; }
public int CreatedUserId { get; set; }
public bool IsActive { get; set; }
public bool Status { get; set; }
public System.DateTime UpdatedDate { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Responses> Responses { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Options> Options { get; set; }
}
}
Option.cs
namespace MerinosSurvey.Models
{
using System;
using System.Collections.Generic;
public partial class Options
{
public int OptionId { get; set; }
public string OptionName { get; set; }
public int QuestionId { get; set; }
public System.DateTime CreatedDate { get; set; }
public System.DateTime UpdatedDate { get; set; }
public bool IsActive { get; set; }
public bool Status { get; set; }
public virtual Questions Questions { get; set; }
}
}
QuestionController.cs - AddOrEdit Action
[HttpPost]
public ActionResult AddOrEdit(Questions question)
{
if (question != null)
{
using (MerinosSurveyEntities db = new MerinosSurveyEntities())
{
Questions questionComing = db.Questions.FirstOrDefault(x => x.QuestionId == question.QuestionId);
if (questionComing != null)
{
//Update
questionComing.QuestionName = question.QuestionName;
questionComing.Status = true;
questionComing.IsActive = true;
questionComing.UpdatedDate = DateTime.Now;
db.Questions.Attach(questionComing);
db.Entry(questionComing).State = EntityState.Modified;
question.QuestionId = questionComing.QuestionId;
db.SaveChanges();
}
else
{
//New Question
question.Status = true;
question.IsActive = true;
question.UpdatedDate = DateTime.Now;
question.CreatedDate = DateTime.Now;
db.Questions.Attach(question);
db.Entry(question).State = EntityState.Added;
db.SaveChanges();
question.QuestionId = question.QuestionId;
}
List<Options> options = question.Options.ToList();
List<Options> existingOptions = new List<Options>(db.Options.Where(x =>
x.Status && x.IsActive && x.QuestionId == question.QuestionId));
foreach (Options existingOption in existingOptions)
{
Options optionUpdated = options.FirstOrDefault(x => x.OptionId == existingOption.OptionId);
if (optionUpdated != null)
{
//Update
existingOption.UpdatedDate = DateTime.Now;
existingOption.OptionName = optionUpdated.OptionName;
existingOption.IsActive = true;
existingOption.Status = true;
db.Options.Attach(existingOption);
db.Entry(existingOption).State = EntityState.Modified;
db.SaveChanges();
options.RemoveAll(x => x.OptionId == existingOption.OptionId);
}
else
{
//Delete
existingOption.Status = false;
existingOption.UpdatedDate = DateTime.Now;
db.Options.Attach(existingOption);
db.Entry(existingOption).State = EntityState.Modified;
db.SaveChanges();
}
}
foreach (Options optionNew in options)
{
optionNew.IsActive = true;
optionNew.Status = true;
optionNew.CreatedDate = DateTime.Now;
optionNew.UpdatedDate = DateTime.Now;
optionNew.QuestionId = question.QuestionId;
db.Options.Add(optionNew);
db.SaveChanges();
}
return Json(new { success = true, message = "Soru başarılı bir şekilde güncellendi."
},
JsonRequestBehavior.AllowGet);
}
}
return Json(new { success = true, message = "Bir problem oluştu." },
JsonRequestBehavior.AllowGet);
}
Your approach is very deliberate, but prone to problems. With EF, the DbContext acts much like a unit of work and SaveChanges should only ever be called once. With something like a related hierarchy where you have a Question with Options, you update and save the question, but then what happens if there is a problem with saving one of the options? You would be committing changes partially and leaving data in an incomplete, inaccurate state.
It's also a LOT of boilerplate code, some of it such as explicitly setting a tracked entity's state to Modified is completely unnecessary. The operation could be revised and simplified down to something like:
[HttpPost]
public ActionResult AddOrEdit(Questions question)
{
if (question == null) // Assert and fail. Avoids nesting.
return Json(new { success = true, message = "Bir problem oluştu." },
JsonRequestBehavior.AllowGet);
using (MerinosSurveyEntities db = new MerinosSurveyEntities())
{
Questions questionComing = db.Questions.Include(x => x.Options).SingleOrDefault(x => x.QuestionId == question.QuestionId); // Prefetch our options...
if (questionComing != null)
{ //Update
questionComing.QuestionName = question.QuestionName;
questionComing.Status = true;
questionComing.IsActive = true;
questionComing.UpdatedDate = DateTime.Now;
// db.Questions.Attach(questionComing); -- not needed, already tracked
// db.Entry(questionComing).State = EntityState.Modified; - Not needed
// question.QuestionId = questionComing.QuestionId; -- Redundant. The ID matches, we loaded based on it.
// db.SaveChanges(); -- No save yet.
// Handle options here... There are probably optimizations that can be added.
var activeOptionIds = question.Options.Where(x => x.Status && s.IsActive).Select(x => x.OptionId).ToList();
foreach(var option in question.Options.Where(x => activeOptionIds.Contains(x.OptionId))
{
var existingOption = questionComing.Options.SingleOrDefault(x => x.OptionId == option.OptionId);
if(existingOption != null)
{ // Update
existingOption.UpdatedDate = DateTime.Now;
existingOption.OptionName = optionUpdated.OptionName;
existingOption.IsActive = true;
existingOption.Status = true;
}
else
{ // New
questionComing.Options.Add(option); // Provided we trust the data coming in. Otherwise new up an option and copy over values.
}
}
var removedOptions = questionComing.Options.Where(x => !activeOptionIds.Contains(x.OptionId).ToList();
foreach(var option in removedOptions)
{
option.Status = option.IsActive = false;
option.UpdatedDate = DateTime.Now;
}
}
else
{ //New Question
// Dangerous to trust the Question coming in. Better to validate and copy values to a new Question to add...
question.Status = true;
question.IsActive = true;
question.UpdatedDate = question.CreatedDate = DateTime.Now;
// db.Questions.Attach(question); -- Add it...
// db.Entry(question).State = EntityState.Added;
// question.QuestionId = question.QuestionId; -- Does nothing...
db.Questions.Add(question); // This will append all Options as well.
}
// Now, after all changes are in, Save.
db.SaveChanges();
return Json(new { success = true, message = "Soru başarılı bir şekilde güncellendi." },JsonRequestBehavior.AllowGet);
} // end using.
}
This I would further break down into private methods to handle the add vs. update. While this doesn't answer how you can update a parent without it's children, it should demonstrate why you should leverage the capabilities of EF to ensure that children are updated with their parents properly. SaveChanges should only ever be called one time within the lifetime scope of a DbContext so that it ensures that all related changes are either committed or rolled back in the event of a failure. EF manages relationships between entities that it is told to track so you can add an entity with new children. Where you need to be careful is with references, such as if you have an existing QuestionType entity associated with a new question. In these scenarios you always want to load the entity within the DbContext scope and use that reference, not a detached reference coming in because EF will treat that as a new entity resulting in duplicate data or duplicate key constraints being hit. It's generally advisable to not pass entities between client and server to avoid issues like this. Attaching or adding entities coming from the client can expose a system to data tampering if not validated properly, and can lead to issues when it comes to referencing existing data.
For instance if you pass in a new question that has a QuestionType reference of "MultipleChoice" (A lookup table of question types) where that is QuestionType ID #1. If you do something like:
db.Questions.Add(question);
"question" was untracked, and all referenced entities are untracked. If you add it, or attach it as a new entity, those referenced entities will be treated as new entities. This would effectively want to Insert a new QuestionType ID #1, leading to a key violation (row already exists) or would insert a new QuestionType ID #12 for example if the QuestionType was configured for incrementing ID. To get around this:
var existingQuestionType = db.QuestionTypes.Single(x => x.QuestionTypeId == question.QuestionType.QuestionTypeId);
question.QuestionType = existingQuestionType; // Points our question type reference at the existing, tracked reference.
db.Questions.Add(question);
question.QuestionType and existingQuestionType would both have an ID of 1 in this example. The difference is that existingQuestionType is tracked/known by the Context where question.QuestionType was an untracked reference. If we added question without the context knowing about the reference, it would treat it like it would a child record of question and want to insert that too. This is probably one of the biggest things that trips people up with EF references and leads to problems and efforts to get more deliberate with related entities, but takes away a lot of the advantages EF can provide. We point our new question reference at the tracked entity so when EF goes to insert the question, it already knows the QuestionType reference as an existing row and everything works as expected.

Why is EF inserting new data for entities that I'm not specifying?

I'm going to chunk this down to as simple a case as I can, but this happens for everything.
I'm basing most of my data model POCO objects on a BaseDataObject defined as follows:
public class BaseDataObject
{
public int Id { get; set; }
public bool Deleted { get; set; }
}
My code-first data model has a Client object:
public class Client : BaseDataObject
{
public string Name { get; set; }
public virtual Category Category { get; set; }
public virtual Category Subcategory { get; set; }
}
The Category object is pretty simple:
public class Category : BaseDataObject
{
public string Name { get; set; }
}
The required Id property exists in the inherited BaseDataObject.
To add entities, I'm using the following repo:
public class DataRepository<TModel, TContext>
where TModel : BaseDataObject
where TContext : DbContext
{
public int AddItem(T item)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
db.Set<T>().Add(item);
db.SaveChanges();
}
}
// These are important as well.
public List<T> ListItems(int pageNumber = 0)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
// Deleted property is also included in BaseDataObject.
return db.Set<T>().Where(x => !x.Deleted).OrderBy(x => x.Id).Skip(10 * pageNumber).ToList();
}
public T GetSingleItem(int id)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
return db.Set<T>().SingleOrDefault(x => x.Id == id && !x.Deleted);
}
}
}
This adds a new client perfectly fine, but there's something weird about my data model here that's causing Entity Framework to also add 2 new Categories every time I add a client based on which categories I'm selecting on my form.
Here's my form's code:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
try
{
BindDropDownList<Category>(CategoryList);
BindDropDownList<Category>(SubcategoryList);
}
// Error handling things
}
}
private void BindDropDownList<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
control.DataSource = repo.ListItems();
control.DataTextField = "Name";
control.DataValueField = "Id";
control.DataBind();
control.Items.Insert(0, new ListItem("-- Please select --", "0"));
}
private TModel GetDropDownListSelection<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
int.TryParse(control.SelectedItem.Value, out int selectedItemId);
return repo.GetSingleItem(selectedItemId);
}
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
Category = selectedCategory,
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}
Unless there's something wrong with the way I'm creating the relationship here (using the virtual keyword or something maybe) then I can't see any reason why this would add new Categories to the database as duplicates of existing ones based on the selections I make in the drop down lists.
Why is this happening? What have I got wrong here?
The DbSet<T>.Add method cascades recursively to navigation properties which are not currently tracked by the context and marks them as Added. So when you do
db.Set<T>().Add(item);
it actually marks both Client class referenced Category entities as Added, hence SaveChanges inserts two new duplicate Category records.
The usual solution is to tell EF that entities are existing by attaching them to the context in advance. For instance, if you replace repo.AddItem(client); with
using (var db = new ApplicationDbContext())
{
if (client.Category != null) db.Set<Category>().Attach(client.Category);
if (client.Subcategory != null) db.Set<Category>().Attach(client.Subcategory);
db.Set<Client>().Add(item);
db.SaveChanges();
}
everything will be fine.
The problem is that you use generic repository implementation which does not provide you the necessary control. But that's your design decision issue, not EF. The above is EF intended way to handle such operation. How you can fit it into your design is up to you (I personally would eliminate the generic repository anti-pattern and use directly the db context).
It is really hard to judge from your listing because no FK mappings are included nor the base model details are provided.
However, it would appear that the Category that you assigned to client does not have PK set, and (most likely) only has the Name set, and you have no unique IX on that.
So EF has no reasonable way to work out that this is the right category.
One way to sort it is
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext>();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
// either
Category = new DataRepository<Category , ApplicationDbContext>().GetSingleItem(selectedCategory.id),
// or, easier (assuming you have FK properties defined on the model)
CategoryId = selectedCategory.Id,
// repeat as needed
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}

How to clone object with a different Primary Key

I have this class Cart_Record, shown below. I want to update the PrimaryKey. To do that I am trying to clone the object into a new object to copy CartLines and update ID. I haven't found much in the issue queue or the documentation to help me.
public class Cart_Record : RealmObject
{
public Cart_Record() { }
public Cart_Record(IList<Cart_Line> cartLines, int id)
{
ID = id;
foreach (var cartLine in cartLines)
CartLines.Add(App.RealmDB.Find<Cart_Line>(cartLine.ProductId));
}
[PrimaryKey]
public int ID { get; set; }
public IList<Cart_Line> CartLines { get; }
}
I am trying this
var appCart = App.RealmDB.All<Cart_Record>().First();
App.RealmDB.Write(() =>
{
var cartLines = new List<Cart_Line>(appCart.CartLines);
App.RealmDB.Remove(App.RealmDB.Find<Cart_Record>(appCart.ID));
App.RealmDB.Add<Cart_Record>(new Cart_Record(cartLines, serverCart.ID));
});
However I keep getting exceptions, specifically RealmObjectManagedByAnotherRealmException. I don't understand how as I am not readding the Cart_Line objects to Realm, just to the CartLine list in the new object.
What am I doing wrong?
Thanks ahead of time.
Edit: I found something that works but I would like to see if someone else have a better method. This is what works for me.
var appCart = App.RealmDB.All<Cart_Record>().First();
App.RealmDB.Write(() =>
{
var cartLines = new List<Cart_Line>(appCart.CartLines);
App.RealmDB.Remove(App.RealmDB.Find<Cart_Record>(appCart.ID));
var newAppCart = App.RealmDB.Add<Cart_Record>(new Cart_Record() { ID = serverCart.ID });
foreach (var cartLine in cartLines)
newAppCart.CartLines.Add(cartLine);
});
I'm not sure what App.RealmDB does under the hood, but using the out-of-the-box Realm API, what you want to achieve can be done by simply adding the CartLines from the original to the updated object:
// Assume want to change Id from 1 to 2
var realm = Realm.GetInstance();
var original = realm.Find<Cart_Record>(1);
var updated = new Cart_Record { ID = 2 }; // other properties must be copied here
foreach (var cart in original.CartLines)
{
updated.CartLines.Add(cart);
}
realm.Write(() =>
{
realm.Remove(original);
realm.Add(updated);
});
// updated now has all the original's CartLines

{"Cannot insert explicit value for identity column in table 'Cantact' when IDENTITY_INSERT is set to OFF."}

I'm trying to save a contact in my program which is a simple phone book in C# and I'm using linq & Entity Framework & when I want to add or change any data I get a run time error
Cannot insert explicit value for identity column in table 'Contact' when IDENTITY_INSERT is set to OFF.
Here's my insert (add) code, on the other hand I don't want to add any data in my primary key which is ID and I want to leave it to my SQL Server.
Thank you all for helping me
public void Save()
{
using (var Contex = new Phone_BookEntities1())
{
var p = from c in Contex.Cantacts
where c.Cantact1 == Name
select new { c.Cantact1, c.Number };
if (!p.Any())
{
Ref_Cantact = new Cantact();
Ref_Cantact.Cantact1 = Name;
Ref_Cantact.Number = Num;
Contex.Cantacts.Add(Ref_Cantact);
Contex.SaveChanges();
}
}
}
EDIT
public partial class Cantact
{
public string Cantact1 { get; set; }
public string Number { get; set; }
public int ID { get; set; }
}
You may do this;
public void Save(string Name, string Num)
{
using(var context = new Phone_BookEntities1())
{
var existingContacts = Context.Cantacts.Where( c=>c.Cantact1 == Name); //there can be many contacts with the same name. Use FirstOrDefault and also improve the filtering criteria
if(existingContacts.Any())
{
foreach(var contact in existingContacts)
{
contact.Number = Num;
}
}else
{
var Ref_Cantact = new Cantact(){Cantact1 = Name, Number = Num};
context.Cantacts.Add(Ref_Cantact);
}
Contex.SaveChanges();
}
}
you can try this: this will wrap all calls in a transaction, therefore setting identity insert on for the insert statement (Created by EF when calling Add+SaveChanges).
if (!p.Any())
{
Ref_Cantact = new Cantact();
Ref_Cantact.Cantact1 = Name;
Ref_Cantact.Number = Num;
using(var trans=Contex.Database.BeginTransaction())
{
Contex.Database.ExecuteSqlStatement("SET IDENTITY_INSERT Contact ON;");
Contex.Cantacts.Add(Ref_Cantact);
Contex.SaveChanges();
trans.Commit();
}
}
EDIT: Another possibility would be setting AutoIncrement (DatabaseGeneratedOption.Identity) off, using (in your modelbuilder in context class (or whereever)):
modelBuilder.Entity<Cantacts>().Property(x=>x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
I needed to update my .edmx class in my model

Ensure that explicitly set primary key values are unique

I am getting following exception on my project:
An exception of type 'System.InvalidOperationException' occurred in
EntityFramework.dll but was not handled in user code
Additional information: Saving or accepting changes failed because of
more than one entity of type 'MyProject.Data.Poco.MyProjectCountry' 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.
the error happens at the following line:
using (MyProjectDataContext context = new MyProjectDataContext())
{
MyProjectItemTag existingItemTag = (from p in context.ItemTags.Include(p => p.MyProjectGenre).Include(p => p.MyProjectCountry)
where p.MyProjectUser.UserId == ItemTag.MyProjectUser.UserId &&
p.MyProjectItem.ItemId == MyProjectItem.ItemId
select p).FirstOrDefault();
// new tag
if (existingItemTag == null)
{
existingItemTag = ItemTag;
existingItemTag.MyProjectItem.ItemId = MyProjectItem.ItemId;
}
// existing tag
else
{
existingItemTag.MyProjectItem = new MyProjectItem { ItemId = MyProjectItem.ItemId };
existingItemTag.MyProjectUser = new MyProjectUser { UserId = ItemTag.MyProjectUser.UserId };
}
// updates
existingItemTag.MyProjectCountry = MyProjectCountry;
if (MyProjectCountry != null)
existingItemTag.MyProjectCountry = new MyProjectCountry()
{
MyProjectCountryId = MyProjectCountry.MyProjectCountryId
};
existingItemTag.MyProjectGenre = MyProjectGenre;
context.Entry(existingItemTag.MyProjectItem).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectUser).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectGenre).State = EntityState.Unchanged;
if (existingItemTag.MyProjectCountry != null)
{
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
}
// db
context.ItemTags.AddOrUpdate(existingItemTag);
context.SaveChanges();
return existingItemTag.ItemTagId;
}
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
My Class:
public class MyProjectItemTag
{
public int ItemTagId { get; set; }
public MyProjectUser MyProjectUser { get; set; }
public MyProjectItem MyProjectItem { get; set; }
public MyProjectCountry MyProjectCountry { get; set; }
public MyProjectGenre MyProjectGenre { get; set; }
public MyProjectMood MyProjectMood { get; set; }
public MyProjectItemTag()
{
}
public MyProjectItemTag(string userId, string providerContentId)
{
MyProjectUser = new MyProjectUser
{
UserId = userId
};
MyProjectItem = new MyProjectItem
{
ProviderContentId = providerContentId
};
}
}
My Config:
public class MyProjectItemTagConfiguration : EntityTypeConfiguration<MyProjectItemTag>
{
public MyProjectItemTagConfiguration()
{
ToTable("MyProjectItemTags");
HasKey(p => p.ItemTagId);
HasRequired(p => p.MyProjectUser);
HasRequired(p => p.MyProjectItem);
HasOptional(p => p.MyProjectCountry);
}
}
What I am missing here?
This is all you really need to look for:
Additional information: Saving or accepting changes failed because more than one entity of type 'MyProject.Data.Poco.MyProjectCountry' have the same primary key value.
The following code may not necessarily populate the MyProjectCountry.
MyProjectItemTag existingItemTag =
(from p in context.ItemTags
.Include(p => p.MyProjectGenre)
.Include(p => p.MyProjectCountry)
where p.MyProjectUser.UserId == ItemTag.MyProjectUser.UserId
&& p.MyProjectItem.ItemId == MyProjectItem.ItemId
select p).FirstOrDefault();
So you set it to some variable you haven't give us any context too...
existingItemTag.MyProjectCountry = MyProjectCountry;
I'd assume it is not null, so you change it's ID which is a Giant Code Smell...
(Why assign it? after all it's already assigned..)
if (MyProjectCountry != null)
existingItemTag.MyProjectCountry = new MyProjectCountry()
{
MyProjectCountryId = MyProjectCountry.MyProjectCountryId
};
Then you tell EF it hasn't changed?? Another Code Smell.
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
So what this tells me is that the Context has already downloaded this entity into its Object Cache, but the one you are assigning is not the one in the cache so when I tries to added to the cache, there is a duplicate.
Try to use context.Model.AddORUpdate(model) Method, you need to add using System.Data.Entity.Migrations as well for this method.
Check in the.edmx file StoreGeneratedPattern. If the Id in the database is auto generated, StoreGeneratedPattern has to be Identity. In my case was None. It's not the best practice to edit the edmx file. I personally deleted the table in the edmx file, I created a new one and after that the StoreGeneratedPattern = Identity.

Categories