Ensure that explicitly set primary key values are unique - c#

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.

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.

Adding Mutliple Row Effected properly but not Updating (C#, Entity Framework 6)

As you can see below, I'm trying to update the "active" and "version" fields, which are properties of the templateData object.
When I want to add a new record, it works fine and meets the needs. But when I try to update the state.modified line, the error is:
Attaching an entity of type '...' failed because another entity already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
Although I have tried many ways of crashing (as you noticed) I have not been successful.
What is the cause of this error? And how can I handle it?
Thank you for your help.
Service:
public ResultObject<TemplateData> SaveTemplateData(TemplateData oTemplateData)
{
var oResult = new ResultObject<TemplateData>();
using (var contextTransaction = _context.Database.BeginTransaction())
{
try
{
var listTempDatas = _context.TemplateDatas.Where(td => td.TemplateRID == oTemplateData.TemplateRID)
.ToList();
#region active/version
oTemplateData.Active = true;
if (listTempDatas.Count > 0)
{
#region resetActives
listTempDatas.ForEach(ltd => ltd.Active = false);
#endregion
#region getMaxVersion
var maxVersionValue = listTempDatas.Max(ltd => ltd.TemplateVersion);
//var maxVersionValue2 = listTempDatas.OrderByDescending(ltd => ltd.TemplateVersion).Select(ltd => ltd.TemplateVersion).First();
oTemplateData.TemplateVersion = maxVersionValue + 1;
#endregion
}
else if (listTempDatas.Count == 0)
{
oTemplateData.TemplateVersion = 1;
}
#endregion
if (oTemplateData.ID > 0)
{
var oldTempData = _context.TemplateDatas.AsNoTracking()
.FirstOrDefault(td => td.ID == oTemplateData.ID);
if (oldTempData != null)
_context.Entry(oTemplateData).State = EntityState.Modified;// and there it is
}
else if (oTemplateData.ID == 0)
{
_context.Entry(oTemplateData).State = EntityState.Added;
}
_context.SaveChanges();
oResult.ResulObject = oTemplateData;
contextTransaction.Commit();
}
catch (Exception e)
{
contextTransaction.Rollback();
oResult.AddError("TemplateService.SaveTemplateData", e.ToString());
}
}
return oResult;
}
Entity:
public class TemplateData
{
...
public int ID { get; set; }
public int? TemplateRID { get; set; }
...
public int? TemplateVersion { get; set; }
public bool? Active { get; set; }
...
public virtual Template Template { get; set; }
}
DataContextExtension:
public static ApplicationDbContext BulkInsert<T>(this ApplicationDbContext context, T entity, int count,
int batchSize) where T : class
{
context.Set<T>().Add(entity);
if (count % batchSize == 0)
{
context.SaveChanges();
context.Dispose();
context = new ApplicationDbContext();
// This is optional
context.Configuration.AutoDetectChangesEnabled = false;
}
return context;
}
The problem is that the line
var listTempDatas = _context.TemplateDatas.Where(td => td.TemplateRID == oTemplateData.TemplateRID)
.ToList();
already loads (tracks) in the context the existing entity you are trying to update.
So first you can try to find it in that list (instead of with a separate database query):
var oldTempData = listTempDatas.FirstOrDefault(td => td.ID == oTemplateData.ID);
and then simply update the properties of the existing entity rather than trying to mark the detached entity as modified:
if (oldTempData != null)
_context.Entry(oldTempData).CurrentValues.SetValues(oTemplateData); // and there you go

{"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

Save detached object graph using Entity Framework code first causes Primary Key violation

I'm trying to save an object graph of POCOs I have mapped to EF6 using Code First fluent notations.
Upon saving the object graph however, I stumble upon primary key violation exceptions.
The object graph is quite simple:
One Issue can contain multiple WorkItems with each one Author (as User).
The objects are populated externally (using a Web API)
When I attempt to save an issue with two workitems which refer to the same author, I would expect the issue to be inserted, the workitems to be inserted and one author to be inserted, and the other one to be referenced or be updated.
What happens however is that the issue is inserted, the workitems are inserted and both references to the same user are inserted, resulting in a primary key violation.
Simplified Issue object:
public class Issue
{
public Issue()
{
WorkItems = new List<WorkItem>();
}
public string Id { get; set; }
private List<WorkItem> _workItems;
public List<WorkItem> WorkItems
{
get { return _workItems ?? new List<WorkItem>(); }
set { _workItems = value; }
}
}
Simplified WorkItem:
public class WorkItem
{
public string Id { get; set; }
public string AuthorLogin
{
get; set;
}
private WorkItemAuthor _author;
public WorkItemAuthor Author
{
get { return _author; }
set { _author = value;
if (value != null)
{
AuthorLogin = value.Login;
}
else
{
AuthorLogin = string.Empty;
}
}
}
}
Simplified user object:
public class User
{
public string Login { get; set; }
public string FullName { get; set; }
}
Their Code-first configurations:
internal IssueConfiguration()
{
HasKey(x => x.Id);
HasMany(x => x.WorkItems);
}
internal WorkItemConfiguration()
{
HasKey(x => x.Id);
HasRequired(p => p.Author)
.WithMany(b => b.WorkItems)
.HasForeignKey(x=>x.AuthorLogin);
}
internal UsersConfiguration()
{
HasKey(x => x.Login);
}
All quite straightforward. Upon database create, de tables look fine and dandy too, with FKs on the columns where one would expect them
Now when saving the issue, it would have been nice if the object graph would be inserted, and the reference to existing objects would be recognized automagically and optionally inserted or referenced only.
I attempt to add issues accordingly:
using (var db = new Cache.Context())
{
if (db.Issues.Any(e => e.Id == issue.Id))
{
db.Issues.Attach(issue);
db.Entry(issue).State = EntityState.Modified;
}
else
{
db.Issues.Add(issue);
}
db.SaveChanges();
}
Is the solution to this issue that I walk through the object graph to manually add or attach the other objects in the graph too? I would expect by defining the proper Foreign Key values these references would be recognized.
I finally ended up doing something similar to this, quite laborious and I would still like to find a better way.
Finding out whether an entity is already attached or exists in the database turned out to be pollute the model too much (implementing IEquatable<T> is fine, but I think implementing IEntityWithKey on my POCOs pollutes the POCO too much. (and till that did not seem to suffice tracking entities in the context)
internal static void Save(this List<Issue> issues)
{
using (var db = new Context())
{
foreach (var issue in issues.ToList())
{
foreach (var workItem in issue.WorkItems.ToList())
{
if (workItem.Author != null)
{
var existing = db.Users.SingleOrDefault(e => e.Login == workItem.Author.Login);
if (existing == null)
{
db.Users.Add(workItem.Author);
}
else
{
//Update existing entities' properties
existing.Url = workItem.Author.Url;
//Replace reference
workItem.Author = existing;
}
db.SaveChanges();
}
var existingWorkItem = db.WorkItems.SingleOrDefault(e => e.Id == workItem.Id);
if (existingWorkItem == null)
{
db.WorkItems.Add(workItem);
}
else
{
//Update existing entities' properties
existingWorkItem.Duration = workItem.Duration;
//Replace reference
issue.WorkItems.Remove(workItem);
issue.WorkItems.Add(existingWorkItem);
}
db.SaveChanges();
}
var existingIssue = db.Issues.SingleOrDefault(x => x.Id == issue.Id);
if (existingIssue == null)
{
db.Issues.Add(issue);
}
else
{
//Update existing entities' properties
existingIssue.SpentTime = issue.SpentTime;
}
db.SaveChanges();
}
}
}
There is a small bug in the Issue object.
"return _workItems ?? new List();" could return a new WorkItem on every get if _workItems ever became null. Here is the fixed version.
public class Issue {
public Issue() {
WorkItems = new List<WorkItem>();
}
public String Id {
get; set;
}
public List<WorkItem> WorkItems { get; private set; }
}

How to check for duplicates before saving?

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)

Categories