Entity Framework and duplicates - c#

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()

Related

Entity Add/Insert with column equal to identity primary key value

During an insert/add can I make a different column equal to the newly valued primary key that's an identity auto generated value all in one write/save process? I know I can grab after fact and change but that's extra database hits I'm trying to avoid.
public class myDataTableRec
{
public int Id { get; set; } //This is an Identity Primary Key
public string Name { get; set; }
public int PostId { get; set; } //Want this the same as Id when it gets generated
}
myrec = new myDataTableRec;
db.myDataTable.Add(myrec);
db.SaveChanges();
From https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.add?view=efcore-5.0
"Add(TEntity)
Begins tracking the given entity, and any other reachable entities that are not already being tracked, in the Added state such that they will be inserted into the database when SaveChanges() is called.
Use State to set the state of only a single entity."
Since it tracks the entity, you can do the following:
myrec=new myDataTableRec;
db.myDataTable.Add(myrec);
myrec.PostId = myrec.Id
db.SaveChanges();
Edit: If it creates the Id after the Save, the you can always create your own Id (Guid.NewGuid()) and don't let EF generate it.
You have to set the property of StoreGeneratedPattern to identity and then you'll be able to achieve this
myrec = new myDataTableRec;
db.myDataTable.Add(myrec);
db.SaveChanges();
var id = myrec.Id;
In SQL it's not possible to insert an entity and set its generated primary in a foreign key column to itself in one atomic operation. That's only possible if the primary key is not generated, i.e. not an identity column.
However, just as EF is capable of inserting related entities in one operation and setting generated key values in foreign keys on the fly, one might argue that EF could support setting a parent to itself.
Take this entity class (replacing your placeholder name and enhancing it with navigation properties):
class Post
{
public int ID { get; private set; }
public string Name { get; set; }
public int? ParentPostID { get; private set; }
public Post ParentPost { get; set; }
public ICollection<Post> ChildPosts { get; private set; }
}
EF could have chosen to support this scenario:
using var db = new MyContext();
var root = new Post { Name = "Root" };
root.ParentPost = root;
db.Set<Post>().Add(root);
But it doesn't. It tries to insert the entity with a ParentPostID equal to the temporary (negative) ID value. Obviously, that's a FK violation.
To do this cleanly you have to add a transaction and set & save the self reference separately:
using var db = new MyContext();
var root = new Post { Name = "Root" };
db.Set<Post>().Add(root);
using var ts = new TransactionScope();
db.SaveChanges();
root.ParentPost = root;
db.SaveChanges();
ts.Complete();
Note that the parent id has to be nullable. Also note that the navigation property allows setting the parent without ever knowing its key value. Some people like to do these things in a DDD style.

Entity Framework creates non-existing column in query

I face the problem that EF creates a column in the query that does not exist in the Oracle database table.
The simplified model which is created by EF looks like this (I use DB first approach):
public partial class USER
{
public int ID { get; set; }
public string NAME { get; set; }
public int PROCESS_ID { get; set; }
public virtual PROCESS PROCESS { get; set; }
}
public partial class PROCESS
{
public PROCESS()
{
this.USER = new HashSet<User>();
}
public int ID { get; set; }
public virtual ICollection<USER> USER { get; set; }
}
I set up the foreign key constraint in the oracle sql developer.
When I try to get the Users for a selected Process like this:
var users = context.Users.Where(u => u.PROCESS_ID == 0);
It produces following error:
ORA-00904: "Extent1"."R1": invalid ID
So i took a look on the produced SQL:
SELECT
"Extent1".ID,
"Extent1".NAME,
"Extent1".R1,
FROM DB.USER "Extent1"
WHERE "Extent1".R1 = :p__linq__0
Of course this produces an error because R1 isn't a column in the table. But I can't figure out where it comes from. It seems like EF can't map the foreign key properly thats why it's also missing in the generated SQL query?
Maybe someone has a tip for me :)
To follow up my comment, here is a link to the conventions.
The convention for a foreign key is that it must have the same data type as the principal entity's primary key property and the name must follow one of these patterns:
[navigation property name][principal primary key property name]Id
[principal class name][primary key property name]Id
[principal primary key property name]Id
Your convention [navigation property name]_ID isn't on the list.
Encountered the same error recently while working with Oracle using DevArt provider. Turned out it was caused by a column name being longer than 30 chars. OP mentioned that the model posted in his question is a simplified one so it still may be the case.

ObjectContext Error with using .AddRange in EF 6

I'm trying to find some help on an error I'm getting when using the .AddRange in EF 6. I'm getting the following error.
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.
As the error states, my records are actually getting added to the table so I don't know where fix the error.
Doing some research I found a bunch of posts where others say that it has to do with the .edmx file and a primary key on the table. Their suggestion is basically to add the PK and then rebuild the .edmx file. This doesn't fit my scenario for two reasons, one is that I'm using EF 6 with DataBase First so there isn't an .edmx file and second is that this is mapped to and Oracle 11 DB and so the identity is created with a trigger (which seems to work when I look at the added records).
Here is my code I'm using as well as the class for the entity.
using (APIcontext db = new APIcontext())
{
if (listLostTime.Count > 0)
{
db.GROUND_HOURS.AddRange(listLostTime);
db.SaveChanges();
}
}
And the entity class
[Table("GROUND_HOURS")]
public partial class GROUND_HOURS
{
[Key]
public decimal RID { get; set; }
[Required]
[StringLength(8)]
public string EMP_ID { get; set; }
[StringLength(2)]
public string COMPANY_CODE { get; set; }
public DateTime OCCURRENCE_DATE { get; set; }
[Required]
[StringLength(25)]
public string PAY_CODE { get; set; }
public decimal PAY_HOURS { get; set; }
public DateTime INSERT_DATE { get; set; }
}
I'm looking for any suggestions.
Decorate the RID property with the attribute DatabaseGenerated( DatabaseGeneratedOption.Identity )
The problem is that entity framework isn't updating the key value RID with the store generated value prior to accepting changes. In your case, with multiple GROUND_HOURS entities created, each will (presumably) have the default RID value of 0. When EF attempts to accept changes, it recognizes than more than one entity has the same key value and complains.
Thanks to #Moho who gave the ultimate fix. This is how I changed the primary key in my entity class to work and is what I used in my application.
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int RID { get; set; }
I was also able to fix it another way just to let others know. First off because this is and Oracle DB the RID (which is my Primary Key) was scaffold as a Decimal. That caused the RID to always be a 0 when I added my object to a list without specifically assigning it a value. To work around that I changed the RID property to an nullable INT and then when I created my list I set the RID=NULL.
[Key]
public int? RID { get; set; }
This is what I did when created my list.
foreach (var item in results)
{
GROUND_HOURS lostTime = new GROUND_HOURS();
lostTime.RID = null;
lostTime.EMP_ID = item.EmployeeId.ToString("D8");
lostTime.COMPANY_CODE = item.CompanyCode.Trim();
lostTime.OCCURRENCE_DATE = item.OccurrenceDate;
lostTime.PAY_CODE = item.PayCode.Trim();
lostTime.PAY_HOURS = item.Hours;
listLostTime.Add(lostTime);
}

Replacement for sqlite-extensions: InsertWithChildren (too many Variables)

i have a project here, were a big amount of data is read from different sources. In a special logic, a data/object-modell is build with these data. So as a result i retrieve a complete SQLite capable object model.
The data were previously written to the SQLite database using a simple:
_connection.InsertWithChildren(model, true);
But, since the source of the data became bigger, this is not possible anymore, cause the Insert method will throw an "too many variables" exception. ;(
Now, i am looking for an replacement for this method. The difficulty here is that within my model, i nearly always have Foreign-Keys in both directions. Parent has Childs, Childs knows Parent.
Performance is not an issue. I don't care if the function needs 10Seconds or 5Minutes. But does anyone have an idea how to handle the Insert, while all Foreign Keys are filled correctly?
If i use a simple
foreach(var entity in _entityList)
_connection.Insert(entity);
the foreign Keys (IDs) are all Guid.Empty;
best regards and cheers,
Chris
Until issue #64 is fixed you can use ReadOnly properties on lists.
For example:
public class Foo
{
[PrimaryKey]
public Guid Id { get; set; }
[OneToMany(ReadOnly = true)]
public List<Bar> Bars { get; set; }
}
public class Bar
{
[PrimaryKey]
public Guid Id { get; set; }
[ForeignKey(typeof(Foo))]
public Guid ParentId { get; set; }
[ManyToOne]
public Foo ParentFoo { get; set; }
}
Will no longer hit the variable limit issue regardless of the operation executed.
You can now insert the elements safely:
// Insert parent 'foo' element
// This won't insert the children or update their foreign keys
conn.InsertWithChildren(foo);
// Insert all children
// This will also update ParentId foreign key if ParentFoo property is set
conn.InsertAllWithChildren(bars)
Or use plain SQLite.Net methods assigning the foreign keys yourself:
conn.Insert(foo);
foreach (var bar in bars) {
bar.ParentId = foo.Id;
conn.Insert(bar);
}

How to insert object graph using EF code-first

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

Categories

Resources