I want to write a function, which receives any Entity object and finds the current value by automatically determined primary key and updates it.
Could you please point me any direction.
public void Update(object entity)
{
using (var _db = new MyEntities())
{
var table = _db.Set(entity.GetType());
//Here we should somehow find the entity object which primary key coincides with the one of entity
var entityObj = table.FindObjectByPrimaryKey(entity);
entityObj.CopyDataFrom(entity);
_db.SaveChanges()
}
}
Is this possible?
you can find primary keys of an entity like this
ObjectContext objectContext = ((IObjectContextAdapter)_db).ObjectContext;
ObjectSet<YourEntity> set = objectContext.CreateObjectSet<YourEntity>();
IEnumerable<string> keyNames = set.EntitySet.ElementType
.KeyMembers
.Select(k => k.Name);
and then use reflection to access their values.
Found the answer.
public void Update(object entity)
{
using (var _db = new MyEntities())
{
_db.Entry(entity).State = System.Data.Entity.EntityState.Modified;
_db.SaveChanges();
}
}
Related
Why DBContext.SaveChanges() doesn't take effect immediately when using another DbContext on the same using statement?
As an example:
public void ChangeStatus(int id)
{
using(DBContext context = new DBContext())
{
var car = context.Cars.FirstOrDefault(x => x.id == id);
car.status = 1;
context.SaveChanges();
UpdateAnotherStatus(id);
}
}
public void UpdateAnotherStatus(int id)
{
using(DBContext context = new DBContext())
{
var car = context.Cars.FirstOrDefault(x => x.id == id);
car.status2 = 2;
context.SaveChanges();
}
}
If I understand you correctly; this should work.... but not for the fetched entity in the first context. Let me explain.
public void ChangeStatus(int id){
using(DBContext firstContext = new DBContext()){
var firstCar = firstContext .Cars.FirstOrDefault(x=>x.id == id);
firstCar .status = 1;
context.SaveChanges();
UpdateAnotherStatus(id);
//at this point, the data set in the secondContext
//did not update `firstCar` because they are completely seperate.
//to overcome this eighter refetch (slow) or use the firstCar object or firstContext
//to update status2
}
}
public void UpdateAnotherStatus(int id){
using(DBContext secondContext = new DBContext()){
var secondCar = secondContext .Cars.FirstOrDefault(x=>x.id == id);
secondCar .status2 = 2;
secondContext .SaveChanges();
}
}
Entity Framework has a change tracker to keep up with all the changes made in the fetched entities. This change tracker lives in the context. So different context has different change trackers, which are not (really) aware of each other.
I have update a generic update method as follows
public virtual void Update<TEntity>(TEntity entity, string modifiedBy =
null) where TEntity : class,IEntity
{
using (var context = new BanyanDbContext())
{
entity.ModifiedDate = DateTime.UtcNow;
entity.ModifiedBy = modifiedBy;
var existingEntiy = context.Set<TEntity>().Find(entity.Id);
context.Entry(existingEntiy).CurrentValues.SetValues(entity);
context.Entry(existingEntiy).State = EntityState.Modified;
Save(context);
}
}
This method is working fine for updating non reference type values, but CurrentValues.SetValues() does not set or update navigation properties.
How can I set navigation property in this scenario.
public TEntity RemoveNavigationProperties(TEntity input)
{
foreach (var item in input.GetType().GetProperties().Where(x => x.PropertyType.Namespace == input.GetType().Namespace))
{
item.SetValue(input, null);
}
return input;
}
This worked for me. (I just needed to null them)
I've got method AddOrUpdateFruitMetaData which should add or update record in FruitMetaData table.
Unfortunately I'm keep getting an errors:
Violation of PRIMARY KEY constraint 'PK_FruitMetaData'. Cannot insert duplicate key in object 'dbo.FruitMetaData'. The duplicate key value is (0, COLOR). The statement has been terminated.
How this is even possible even when I'm trying to find existing record before?
Also using AddOrUpdate() from System.Data.Entity.Migrations instead of Add() is not helping me.
Call stack looks that:
In my main class in Parallel.ForEach with multiple for loop looking like that:
Parallel.ForEach(args)
{
for(something)
{
var fruit = new Fruit();
fruit.FruitId = uniqueId;
UpdateOrCreateFruitMetaData(fruit, "Color", "Blue")
}
}
I'm calling method:
private void UpdateOrCreateFruitMetaData(Fruit fruit, string metaType, string value)
{
var fruitMetaData = new FruitMetaData();
fruitMetaData.FruitId = fruit.FruitId;
fruitMetaData.MetaType = metaType;
fruitMetaData.Value = value;
using (var db = Context.DB)
{
db.AddOrUpdateFruitMetaData(fruitMetaData);
}
}
That method is using Context.DB which is object containing new DBEntity() and also Dispose(). So my Entity Context is disposed every time.
Then I'm calling next method AddOrUpdateFruitMetaData():
AddOrUpdateFruitMetaData(FruitMetaData fruitMetaData)
{
lock (thisLock)
{
try
{
var fmd = db.FruitMetaData.Where(x => x.FruitId == fruitMetaData.FruitId)
.Where(x => x.MetaType == fruitMetaData.MetaType)
.FirstOrDefault();
if (fmd == null)
db.FruitMetaData.Add(fruitMetaData);
else
fmd.Value = fruitMetaData.Value;
db.SaveChanges();
}
catch (Exception ex)
{
Log.Error($"AddOrUpdateFruitMetaData. FruitId:{fruitMetaData.FruitId} MetaType:{fruitMetaData.MetaType}", ex);
}
}
}
[EDIT] To explain using (var db = Context.DB) :
My Context class contains DbData DB Property which looks like that :
public DbData DB
{
get { return new DbData(); }
}
And DbData class looks like that:
public class DbData : IDisposable
{
private DBEntity db;
public DbData()
{
db = new FruitDBEntity();
}
// This class contains that problematic method
AddOrUpdateFruitMetaData(FruitMetaData fruitMetaData)(...)
}
So each time I'm getting Context.DB property I am actually creating new Entity Context.
I have an entity which is not connected to my dbcontext. I want to change that. However there is already another instance of the same entity attached to dbcontext. If I just add my new entity, I get an error, that an entity with the same primary key is already attached.
I tried multiple different variants of removing the old entity from dbcontext without any success. How can I replace the old instance with the new one?
Note: I don't want to copy the values, I want to attach this very instance of my new entity to dbcontext.
var entity = new MyEntity { Id = 1 };
var logicalDuplicate = dbcontext.Set<MyEntity >().Local
.FirstOrDefault(e => e.Equals(entity));
if (logicalDuplicate != null)
{
// remove logicalDuplicate from dbcontext
}
dbcontext.MyEntity.Attach(entity);
For clarification: I have overridden Equals to check for Id instead of reference.
Try this:
if (logicalDuplicate != null)
{
dbcontext.Entry(logicalDuplicate).State = EntityState.Detached;
dbcontext.MyEntity.Attach(entity);
dbcontext.Entry(entity).State = EntityState.Modified;
}
else
{
dbcontext.MyEntity.Add(entity);
}
How to get related entries
I investigated that and want to share with my results.
I used reflection as short way to get entity properties names. But it's possible to get it without reflection as mentioned #Florian Haider. You can use
answer and this.
// Found loaded related entries that can be detached later.
private HashSet<DbEntityEntry> relatedEntries;
private DbContext context;
private List<string> GetPropertiesNames(object classObject)
{
// TODO Use cache for that.
// From question https://stackoverflow.com/questions/5851274/how-to-get-all-names-of-properties-in-an-entity
var properties = classObject.GetType().GetProperties(BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.Instance);
return properties.Select(t => t.Name).ToList();
}
private void GetRelatedEntriesStart(DbEntityEntry startEntry)
{
relatedEntries = new HashSet<DbEntityEntry>();
// To not process start entry twice.
relatedEntries.Add(startEntry);
GetRelatedEntries(startEntry);
}
private void GetRelatedEntries(DbEntityEntry entry)
{
IEnumerable<string> propertyNames = GetPropertiesNames(entry.Entity);
foreach (string propertyName in propertyNames)
{
DbMemberEntry dbMemberEntry = entry.Member(propertyName);
DbReferenceEntry dbReferenceEntry = dbMemberEntry as DbReferenceEntry;
if (dbReferenceEntry != null)
{
if (!dbReferenceEntry.IsLoaded)
{
continue;
}
DbEntityEntry refEntry = context.Entry(dbReferenceEntry.CurrentValue);
CheckReferenceEntry(refEntry);
}
else
{
DbCollectionEntry dbCollectionEntry = dbMemberEntry as DbCollectionEntry;
if (dbCollectionEntry != null && dbCollectionEntry.IsLoaded)
{
foreach (object entity in (ICollection)dbCollectionEntry.CurrentValue)
{
DbEntityEntry refEntry = context.Entry(entity);
CheckReferenceEntry(refEntry);
}
}
}
}
}
private void CheckReferenceEntry(DbEntityEntry refEntry)
{
// Add refEntry.State check here for your need.
if (!relatedEntries.Contains(refEntry))
{
relatedEntries.Add(refEntry);
GetRelatedEntries(refEntry);
}
}
Edit This finds the original product, removes it, and adds the new one:
static void UpdateDatabase()
{
Context context = new Context();
Product product1 = context.Products.Find(1);
context.Products.Remove(product1);
Product product2 = new Product(){ProductId = 1, Name = "Product2"};
context.Products.Add(product2);
context.SaveChanges();
}
Best way to salve this problem is
db is my database Object
updateprice is my database entity object
ep is my old same database entity object
db.Entry(updateprice).CurrentValues.SetValues(ep);
Please advise, I'm using generic repository with UOW, and I got this error when I'm testing my InsertOrUpdate method.(I'm new in both c# and EF)
Result Message:
Test method UnitTestProject1.ManifestUOW.ManifestUOWTest threw exception:
System.InvalidOperationException: Attaching an entity of type 'DomainClasses.ManifestDetail'
failed because another entity of the same type 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 yet 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.
My generic repository
public T FindById(object id)
{
return _set.Find(id);
}
public void Add(T entity)
{
DbEntityEntry entry = this._ctx.Entry(entity);
if (entry.State != EntityState.Detached)
{
entry.State = EntityState.Added;
}
else
{
this._set.Add(entity);
}
}
public void Update(T entity)
{
DbEntityEntry entry = this._ctx.Entry(entity);
if (entry.State != EntityState.Detached)
{
this._set.Attach(entity);
}
entry.State = EntityState.Modified;
}
public void InsertOrUpdate(T entity, object id)
{
var _Record = FindById(id);
if (_Record != null)
{
Update(entity);
}
else
{
Add(entity);
}
}
My test implementation
[TestMethod]
public void ManifestUOWTest()
{
ApplicationUOW appUOW = new ApplicationUOW();
ManifestDetail manD=new ManifestDetail();
for (var i = 20; i <= 22; i++)
{
manD = new ManifestDetail();
manD.ID = "kkke" + i;
manD.ManifestID = "kkke";
manD.ModifiedDate = DateTime.Now;
manD.PriorityID = 1;
manD.JobNo = "8888777";
manD.PartNo = "ppppp";
manD.OpSeq = "9000";
manD.QTY = 9;
manD.Comment = "";
manD.LitNO = "Lit no";
appUOW.ManifestDetails.InsertOrUpdate(manD, manD.ID);
}
var man = new Manifest();
man.ID = "kkke";
man.CreatedDate = DateTime.Now;
man.ManifestStateID = 2;
man.MFBldgID = 1;
man.MFDestBldgID = 2;
man.UserID = "X6344";
appUOW.Manifests.InsertOrUpdate(man, man.ID);
appUOW.SaveChanges();
}
My UOW
namespace DataLayer
{
public class ApplicationUOW:IDisposable
{
private AuditorStationDB _context = new AuditorStationDB();
private IRepository<Manifest> _manifests = null;
public IRepository<Manifest> Manifests
{
get
{
if (this._manifests == null)
{
this._manifests = new GenericRepository<Manifest>(this._context);
}
return this._manifests;
}
}
private IRepository<ManifestDetail> _manifestDetails = null;
public IRepository<ManifestDetail> ManifestDetails
{
get
{
if (this._manifestDetails == null)
{
this._manifestDetails = new GenericRepository<ManifestDetail>(this._context);
}
return this._manifestDetails;
}
}
public void SaveChanges()
{
this._context.SaveChanges();
}
public void Dispose()
{
if (this._context != null)
{
this._context.Dispose();
}
}
}
}
The problem you are having is by your InsertOrUpdate() method calling the Update() method.
You first asked EF to locate your entity by Id, so at this point, in EF it is tracking that entity.
Then you pass in the T entity into Update(), in which your Update() method ask it to Attach() it to the EF.
Now the EF already tracked a same entity at step 1, you are trying to ask it to Attach() same entity(both
entity has same primary key) at step 2, that is why it failed.
So to solve your problem, there is possible 2 ways (not tested):
don't use FindById() to determine InsertOrUpdate() - maybe if id = 0 will mean need insert else update.
before calling Update(), detach your entity FindById() - this._ctx.Entry(entity).State = EntityState.Detached
Although in this article (http://msdn.microsoft.com/en-us/data/jj592676.aspx), it is not talking about Generic repository, but the idea of how to implement InsertOrUpdate method in EF can be found at bottom.