In my MVC4 project, I am trying to insert some data, that I populated from an excel worksheet, into database (Oracle 10g DB using 12c ODAC) by using EF5.
Story:
First I read data from excel and then convert them to entities. However excel data has no Primary Key (ID) column (I DON'T HAVE CONTROL OVER EXCEL DATA). Therefore for every row in excel first I am searching DB for a match. If there is a match I am getting the matched entity by using the DbContext. In this case my instantiated class/entity has ID property filled.
On the other hand, if there is no match in the DB I am instantiating a new class of entity type without an ID information. If entity exists in more than one row of excel worksheet then my final List would have more than one instance of the same entity each have ID = 0.
After populating the data I add my entities to my DbContext one by one and call SaveChanges() method of the context. Then when I check the Database I see that different instances of the same class getting different IDs, but this is not what I want.
I understand why this is happening since DbContext cannot relate different instances of a class if the PK is not set. All entities's EntityState becomes Added :(
My question is:
"Is there a way to tell Entity Framework that if "all the properties of an entity are equal to each other && the PK/ID is not set" treat these entities as same entities when inserting into database?".
Simplified:
After executing MyMethod() in below code, I want only one row of data created in my database Table:
// I implemented IEquatable to see if it works for EF (I heard it works in NHibernate) but unfortunately not working.
public class MyEntity(): IEquatable<MyEntity>
{
// This is an autoincrementing PK in DB
public int ID { get; set; }
public string Name { get; set; }
public bool Equals(MyEntity other)
{
if (other == null) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(this.ID, other.ID) && Equals(this.Name, other.Name);
}
public override bool Equals(object obj)
{
return Equals(obj as MyEntity);
}
public override int GetHashCode()
{
unchecked
{
var result = this.ID.GetHashCode();
result = (result * 397) ^ (this.Name!= null ? this.Name.GetHashCode() : 0);
return result;
}
}
}
public void MyMethod()
{
DbContext db = new DbContext();
db.MyEntity.Add(new MyEntity { Name = "Foo" }) // ID=0 initially.
db.MyEntity.Add(new MyEntity { Name = "Foo" }) // ID=0 initially.
// When I check, the Entities in ChangeTracker are Equal!
// This is equal to 1.
int distinctNewEntryCount = db.ChangeTracker.Entries<MyEntity>().Select(e => e.Entity).Distinct().Count();
// This is equal to 2.
// But I want it to be 1 (1 Added, 1 Unchanged or only 1 Added).
int newEntryCount = db.ChangeTracker.Entries<MyEntity>().Count(e => e.State == System.Data.EntityState.Added);
db.SaveChanges();
}
By the way I am using an Oracle DB and in my EDMX file I set "StoreGeneratedPattern" attribute of autoincrement PK as "Identity" and assigning ID values to inserted entities by using before insert triggers which calls sequence.nextval() in them.
DbSet.Add returns the entity that was added to the DbSet, maybe it's returning a different instance than the one passed in. Also, what does SaveChanges return? It's supposed to return the # of objects added, updated and deleted. Does it match the # of objects you're trying to insert or update?
"If entity exists in more than one row of excel worksheet then my final List would have more than one instance of the same entity each have ID = 0."
That is also probably a problem. You're saying duplicate/shared rows will get their own unique entity instance with an ID of 0. They should be using the same instance.
Related
This relates to my previous question from a few days ago: EF Core duplicate keys: The instance of entity type '' cannot be tracked because another instance with the key value '' is already being tracked
I am using context.ChangeTracker.TrackGraph to give me more control when inserting data. The insert process works great as show by my own answer on the question above. This problem results when I update items and attempt to save them.
My primary model is as follows (type names have been changed for simplicity):
public class Model
{
public List<Data1> List1 { get; set; }
...
}
Data1 looks like this:
public class Data1
{
public string Name{ get; set; }
...
}
To update to the database, I use this:
using var context = Factory.CreateDbContext();
context.ChangeTracker.TrackGraph(model, node =>
{
if (!(node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)) && node.Entry.IsKeySet)) //this is a workaround but it solves the pk problems I have been having
{
node.Entry.State = EntityState.Modified;
}
else if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Added;
}
});
return await context.SaveChangesAsync();
This works great as it prevents my existing entities (inheriting fromEFChangeIgnore) from being inserted and otherwise handles updates and inserts of other entities well. However, if I have more than one Data item in List1, I get the error:
The instance of entity type 'Data1' cannot be tracked because another instance with the key value '{ID: 0}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I understand this error but I am confused as to why it is appearing in this scenario. Yes, both entities have an ID of zero, but only because they have yet to be inserted to the database. What am I doing wrong here?
It turns out my bug was due to a misplaced parenthesis. Go figure.
In my SaveChanges I had accidently negated the entire conditional at the top, such that entities that should have been Modified became Added, and vice versa. This led to multiple entities with an ID of 0 that I intended to add being marked as Modified. EF then complained that two existing entities had the same ID.
That is, this line:
if (!(node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)) && node.Entry.IsKeySet))
now becomes (conditionals swapped for clarity):
if (node.Entry.IsKeySet && (!node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore))))
And my entire SaveChanges method now is:
using var context = Factory.CreateDbContext();
context.ChangeTracker.TrackGraph(model, node => //all entities inheriting from EFChangeIgnore will not be touched by the change tracker. Other entities with keys will be modified, and items without keys will be added
{
if (node.Entry.IsKeySet && (!node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)))) //this is a workaround but it solves key duplication problems
{
node.Entry.State = EntityState.Modified;
}
else if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Added;
}
return await context.SaveChangesAsync();
I have the following
class Human
int id
string gender
List<PhysicalAttribute> attributes
class PhysicalAttributes
int id
string description
string type
List<Human> humans
When I add the first human with attributes, the tables are created and the data is populated properly.
The problem is when I add the next humans with similar attributes. Let's say I have attributes
type:"body"
description:"slim"
for both the first and second human. When I create new and add the second human, another entry with the same type and description is added to the PhysicalAttributes table.
Is there some way of doing this so that the existing entry will be used?
Do I have to do a lookup on the PhysicalAttributes table first to see that the entry has been created?
Is there some way of doing this so that the existing entry will be used?
Yes. Make (type,description) the Entity Key for PhysicalAttributes. You seem to have introduced a meaningless ID key property that allows multiple PhysicalAttributes to have the same type and description.
It is also possible to not fix the model, and fetch the existing PhysicalAttributes from the database to discover if any of the ones you need already exist. But that's just a tedious workaround for having the wrong entity key structure.
If you are loading from JSON it's going to be inconventient to attach existing entities, and instead you can do the following.
Override SaveChanges and fetch all the PhyscialAttribute values from the database. Then for the PhysicalAttribute entities that already exist in the database, Detach them from the DbContext, and EF won't attempt to insert them.
After you call PhysicalAttributes.Load(), you will have two entries in the ChangeTracker for every PhysicalAttribute on a new Entity that already exists in the database.
EG
public override int SaveChanges()
{
PhysicalAttributes.Load();
var entryGroup = from entry in ChangeTracker.Entries<PhysicalAttribute>()
group entry by new { entry.Entity.Description, entry.Entity.Type } into byKey
where byKey.Any( e => e.State == EntityState.Unchanged)
select byKey;
foreach (var eg in entryGroup)
{
foreach (var e in eg )
{
if (e.State == EntityState.Added)
{
e.State = EntityState.Detached;
}
}
}
return base.SaveChanges();
}
I believe this is asked somewhere else but I can't find straight solution.
My Api is passing object model and on the server side every value of that object which is not passed is considered null (makes sense).
Is there way I can tell EF6 not to update entity with null values from passed object in manner I don't have to write each property and check if it's null.
Pseudo code
API
Update(int id, TaskEntity obj)
{
unitOfWork.Tasks.Update(id, userTask);
...
unitOfWork.Save()
}
Repo update
Update(int id, T entity)
{
var existingRecord = Get(id); //Gets entity from db based on passed id
if (existingRecord != null)
{
var attachedEntry = Context.Entry(existingRecord);
attachedEntry.CurrentValues.SetValues(entity);
}
}
My problem is that any data with null values will actually rewrite existing db record value with nulls.
Please point me to a solution or article where this is solved. Should I go reflections, maybe automapper could handle this (it's not its purpose i believe), or some kind of helper method should be written, as my objects can contain sub object.
Thank you in advance.
You can do something like this
Update(int id, T entity,string[] excludedFields)
{
var existingRecord = Get(id); //Gets entity from db based on passed id
if (existingRecord != null)
{
var attachedEntry = Context.Entry(existingRecord);
attachedEntry.CurrentValues.SetValues(entity);
for(var field in excludedFields)
{
attachedEntry.Property(field).IsModified = false;
}
}
}
some scenaries requires you to update part of the object and sometimes other parts, the best way in my opinion is to pass the fields to exclude from the update
hope it will help you
Personally not a big fan of hitting database and doing a get operation before doing an update. May be while doing the ajax call, you can send a list of properties which you should update (so that the scenario where updating to null values (erasing existing ones) will also be handled).
I'm doing small modifications to what #Hadi Hassan has done (without hitting database for getting the entity):
Update(T entity,string[] includedFields)
{
var existingRecord = Context.Attach(entity); // assuming primary key (id) will be there in this entity
var attachedEntry = Context.Entry(existingRecord);
for(var field in includedFields)
{
attachedEntry.Property(field).IsModified = true;
}
}
Note - attachedEntry.Property(field).IsModified will not work for related entities
I have a new project using Entity Framework that is using Database First. This is because it looks at sql data that already exists on several different customers sites. The one issue we sometimes find is where there are slight discrepancies between the database our end and the database at a customer. For example we may have a field that is nullable and on some customers its set to not null which then causes an EF error at their end. I don't know whey they are different as they existed before I got anywhere near them.
I have the following code already which loops through the dbsets but I can't seem to create a sql query that passes the dbset type to run a query against it to test if the model matches the database, can anyone help?
System.Data.Entity.Core.Objects.ObjectContext ctx = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)db).ObjectContext;
System.Data.Entity.Core.Metadata.Edm.MetadataWorkspace workspace = ctx.MetadataWorkspace;
IEnumerable<System.Data.Entity.Core.Metadata.Edm.EntityType> tables = workspace.GetItems<System.Data.Entity.Core.Metadata.Edm.EntityType>(System.Data.Entity.Core.Metadata.Edm.DataSpace.SSpace);
foreach (var table in tables)
{
var tableSchema = table.MetadataProperties["TableName"].Value.ToString();
// Need something here to dynamically select all results
// If could pass the Entity Type to db.Database.SqlQuery<type> problem would be solved
}
You could use the built in method of entity framework
context.Database.CompatibleWithModel(true);
Or you could read the systables of the database.
This code may not match your sql server!
SELECT TABLE_NAME, COLUMN_NAME, DOMAIN_NAME, NULLS, WIDTH FROM SYSTABLE LEFT JOIN SYSCOLUMN ON SYSTABLE.TABLE_ID = SYSCOLUMN.TABLE_ID LEFT JOIN SYSDOMAIN ON SYSCOLUMN.DOMAIN_ID = SYSDOMAIN.DOMAIN_ID WHERE SYSTABLE.TABLE_NAME = 'YourTableHere'
This query will return all columns of a table, the types and if the column is nullable. Then compare this query result against your model. You can use the metadata or you read the properties from the classes with GetProperties.
public class DbColumnTypes {
public String TABLE_NAME { get; set; };
public String COLUMN_NAME { get; set; };
public String DOMAIN_NAME { get; set; };
public Boolean ISNULLABLE { get; set; };
}
List<DbColumnTypes> dbColumns; //Read that from your DB
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public).ToArray();
foreach(PropertyInfo item in properties)
{
if(dbColumns.Any(x => x.COLUMN_NAME == item.Name) {
//compare the Database types to the C# types (varchar with string, etc.)
if(item.Property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)
{
//check if database column is nullable
}
}
}
You can wrap that code in a method an call it for every class you have in your ef model. With this you can check if your database matches your model.
To get all types in your model you could enumerate all types in a namespace:
var theList = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.Namespace == "your.name.space").ToList();
With this list you can go trough all classes and call the method above.
In Entity Framework - Is there any way to retrieve a newly created ID (identity) inside a transaction before calling 'SaveChanges'?
I need the ID for a second insert, however it is always returned as 0...
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
objectContext.Connection.Open();
using (var transaction = objectContext.Connection.BeginTransaction())
{
foreach (tblTest entity in saveItems)
{
this.context.Entry(entity).State = System.Data.EntityState.Added;
this.context.Set<tblTest>().Add(entity);
int testId = entity.TestID;
.... Add another item using testId
}
try
{
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
objectContext.Connection.Close();
throw ex;
}
}
objectContext.Connection.Close();
The ID is generated by the database after the row is inserted to the table. You can't ask the database what that value is going to be before the row is inserted.
You have two ways around this - the easiest would be to call SaveChanges. Since you are inside a transaction, you can roll back in case there's a problem after you get the ID.
The second way would be not to use the database's built in IDENTITY fields, but rather implement them yourself. This can be very useful when you have a lot of bulk insert operations, but it comes with a price - it's not trivial to implement.
EDIT: SQL Server 2012 has a built-in SEQUENCE type that can be used instead of an IDENTITY column, no need to implement it yourself.
As others have already pointed out, you have no access to the increment value generated by the database before saveChanges() was called – however, if you are only interested in the id as a means to make a connection to another entity (e.g. in the same transaction) then you can also rely on temporary ids assigned by EF Core:
Depending on the database provider being used, values may be generated client side by EF or in the database. If the value is generated by the database, then EF may assign a temporary value when you add the entity to the context. This temporary value will then be replaced by the database generated value during SaveChanges().
Here is an example to demonstrate how this works. Say MyEntity is referenced by MyOtherEntity via property MyEntityId which needs to be assigned before saveChanges is called.
var x = new MyEntity(); // x.Id = 0
dbContext.Add(x); // x.Id = -2147482624 <-- EF Core generated id
var y = new MyOtherEntity(); // y.Id = 0
dbContext.Add(y); // y.Id = -2147482623 <-- EF Core generated id
y.MyEntityId = x.Id; // y.MyEntityId = -2147482624
dbContext.SaveChangesAsync();
Debug.WriteLine(x.Id); // 1261 <- EF Core replaced temp id with "real" id
Debug.WriteLine(y.MyEntityId); // 1261 <- reference also adjusted by EF Core
The above also works when assigning references via navigational properties, i.e. y.MyEntity = x instead of y.MyEntityId = x.Id
If your tblTest entity is connected to other entities that you want to attach, you don't need to have the Id to create the relation. Lets say tblTest is attached to anotherTest object, it the way that in anotherTest object you have tblTest object and tblTestId properties, in that case you can have this code:
using (var transaction = objectContext.Connection.BeginTransaction())
{
foreach (tblTest entity in saveItems)
{
this.context.Entry(entity).State = System.Data.EntityState.Added;
this.context.Set<tblTest>().Add(entity);
anotherTest.tblTest = entity;
....
}
}
After submitting the relation would be created and you don't need to be worry about Ids and etc.
You can retreive an ID before calling .SaveChanges() by using the Hi/Lo alhorithm. The id will be assigned to the object once it is added to dbcontext.
Example configuration with fluent api:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>(e =>
{
e.Property(x => x.Id).UseHiLo();
});
}
An excerpt from the relevant Microsoft article:
The Hi/Lo algorithm is useful when you need unique keys before committing changes. As a summary, the Hi-Lo algorithm assigns unique identifiers to table rows while not depending on storing the row in the database immediately. This lets you start using the identifiers right away, as happens with regular sequential database IDs.
#zmbq is right, you can only get the id after calling save changes.
My suggestion is that you should NOT rely on the generated ID's of the database.
The database should only a detail of your application, not an integral and unchangeable part.
If you can't get around that issue use a GUID as an identifier due it's uniqueness.
MSSQL supports GUID as a native column type and it's fast (though not faster than INT.).
Cheers
A simple work around for this would be
var ParentRecord = new ParentTable () {
SomeProperty = "Some Value",
AnotherProperty = "Another Property Value"
};
ParentRecord.ChildTable.Add(new ChildTable () {
ChildTableProperty = "Some Value",
ChildTableAnotherProperty = "Some Another Value"
});
db.ParentTable.Add(ParentRecord);
db.SaveChanges();
Where ParentTable and ChildTable are two tables connected with Foregin key.
You can look up the value in the ChangeTracker like this:
var newEntity = new MyEntity();
var newEntity.Property = 123;
context.Add(newEntity);
//specify a logic to identity the right entity here:
var entity = context.ChangeTracker.Entries()
.FirstOrDefault(e => e.Entity is MyEntity myEntity &&
myEntity.Property == newEntity.Property);
//In this case we look up the value for an autogenerated id of type int/long
//it will be a negative value like -21445363467
var value = entity.Properties?
.FirstOrDefault(pe => pe.Metadata.GetColumnName() == nameof(MyEntity.Id))?.CurrentValue;
//if you set it on another entity, it will be replaced on SaveChanges()
My setup was mysql 5.7, but should work in other environments also.