Entity Framework C# SaveChanges doesn't take effect immediately - c#

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.

Related

Entity Framework, cannot delete record after just adding it

I'm making a simple WPF application that uses one table in a DB. I'm using Entity Framework. Here's how I add my new record:
public static bool CreateNew(CustomerModel newCustomer)
{
var addCustomer = new Customer
{
ID = newCustomer.ID,
Name = newCustomer.Name,
Address = newCustomer.Address,
City = newCustomer.City,
Country = newCustomer.Country
};
try
{
//_context.Customers.Add(addCustomer);
_context.Entry(addCustomer).State = EntityState.Added;
_context.SaveChanges();
return true;
}
catch
{
return false;
}
}
Works fine: record appears in DB.
Now I try to delete the record that was just added based on its ID:
public static bool Delete(long id)
{
var cust = new Customer() { ID = id };
try
{
_context.Entry(cust).State = EntityState.Deleted;
/*_context.Customers.Attach(cust);
_context.Customers.Remove(cust);*/
_context.SaveChanges();
return true;
}
catch
{
return false;
}
}
Doesn't work.
It seems like the DbSet within the application does not hold the entry that was added to the DB. How do I fix this?
PS. Customer class is my POCO entity and CustomerModel is the class I use for the application. _context references the DbContext Entity Framework uses
Try this instead. Use Find method like this:
var cust = _context.Customers.Find(id);
_context.Customers.Remove(cust);
_context.SaveChanges();

InsertOrUpdate Generic Repository

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.

Delete a single record from Entity Framework?

I have a SQL Server table in Entity Framework named employ with a single key column named ID.
How do I delete a single record from the table using Entity Framework?
It's not necessary to query the object first, you can attach it to the context by its id.
Like this:
var employer = new Employ { Id = 1 };
ctx.Employ.Attach(employer);
ctx.Employ.Remove(employer);
ctx.SaveChanges();
Alternatively, you can set the attached entry's state to deleted :
var employer = new Employ { Id = 1 };
ctx.Entry(employer).State = EntityState.Deleted;
ctx.SaveChanges();
You can use SingleOrDefault to get a single object matching your criteria, and then pass that to the Remove method of your EF table.
var itemToRemove = Context.Employ.SingleOrDefault(x => x.id == 1); //returns a single item.
if (itemToRemove != null) {
Context.Employ.Remove(itemToRemove);
Context.SaveChanges();
}
var stud = (from s1 in entities.Students
where s1.ID== student.ID
select s1).SingleOrDefault();
//Delete it from memory
entities.DeleteObject(stud);
//Save to database
entities.SaveChanges();
Employer employer = context.Employers.First(x => x.EmployerId == 1);
context.Customers.DeleteObject(employer);
context.SaveChanges();
I am using entity framework with LINQ. Following code was helpful for me;
1- For multiple records
using (var dbContext = new Chat_ServerEntities())
{
var allRec= dbContext.myEntities;
dbContext.myEntities.RemoveRange(allRec);
dbContext.SaveChanges();
}
2- For Single record
using (var dbContext = new Chat_ServerEntities())
{
var singleRec = dbContext.ChatUserConnections.FirstOrDefault( x => x.ID ==1);// object your want to delete
dbContext.ChatUserConnections.Remove(singleRec);
dbContext.SaveChanges();
}
More generic approuch
public virtual void Delete<T>(int id) where T : BaseEntity, new()
{
T instance = Activator.CreateInstance<T>();
instance.Id = id;
if (dbContext.Entry<T>(entity).State == EntityState.Detached)
{
dbContext.Set<T>().Attach(entity);
}
dbContext.Set<T>().Remove(entity);
}
Just wanted to contribute the three methods I've bounced back and forth with.
Method 1:
var record = ctx.Records.FirstOrDefault();
ctx.Records.Remove(record);
ctx.SaveChanges();
Method 2:
var record = ctx.Records.FirstOfDefault();
ctx.Entry(record).State = EntityState.Deleted;
ctx.SaveChanges();
ctx.Entry(record).State = EntityState.Detached;
One of the reasons why I prefer to go with Method 2 is because in the case of setting EF or EFCore to QueryTrackingBehavior.NoTracking, it's safer to do.
Then there's Method 3:
var record = ctx.Records.FirstOrDefault();
var entry = ctx.Entry(record);
record.DeletedOn = DateTimeOffset.Now;
entry.State = EntityState.Modified;
ctx.SaveChanges();
entry.State = EntityState.Detached;
This utilizes a soft delete approach by setting the record's DeletedOn property, and still being able to keep the record for future use, what ever that may be. Basically, putting it in the Recycle Bin.
Also, in regards to Method 3, instead of setting the entire record to being modified:
entry.State = EntityState.Modified;
You would also simply set only the column DeletedOn as modified:
entry.Property(x => x.DeletedOn).IsModified = true;
With Entity Framework 6, you can use Remove.
Also it 's a good tactic to use using for being sure that your connection is closed.
using (var context = new EmployDbContext())
{
Employ emp = context.Employ.Where(x => x.Id == id).Single<Employ>();
context.Employ.Remove(emp);
context.SaveChanges();
}
[HttpPost]
public JsonResult DeleteCotnact(int id)
{
using (MycasedbEntities dbde = new MycasedbEntities())
{
Contact rowcontact = (from c in dbde.Contact
where c.Id == id
select c).FirstOrDefault();
dbde.Contact.Remove(rowcontact);
dbde.SaveChanges();
return Json(id);
}
}
What do you think of this, simple or not, you could also try this:
var productrow = cnn.Product.Find(id);
cnn.Product.Remove(productrow);
cnn.SaveChanges();
Using EntityFramework.Plus could be an option:
dbContext.Employ.Where(e => e.Id == 1).Delete();
More examples are available here
u can do it simply like this
public ActionResult Delete(int? id)
{
using (var db = new RegistrationEntities())
{
Models.RegisterTable Obj = new Models.RegisterTable();
Registration.DAL.RegisterDbTable personalDetail = db.RegisterDbTable.Find(id);
if (personalDetail == null)
{
return HttpNotFound();
}
else
{
Obj.UserID = personalDetail.UserID;
Obj.FirstName = personalDetail.FName;
Obj.LastName = personalDetail.LName;
Obj.City = personalDetail.City;
}
return View(Obj);
}
}
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int? id)
{
using (var db = new RegistrationEntities())
{
Registration.DAL.RegisterDbTable personalDetail = db.RegisterDbTable.Find(id);
db.RegisterDbTable.Remove(personalDetail);
db.SaveChanges();
return RedirectToAction("where u want it to redirect");
}
}
model
public class RegisterTable
{
public int UserID
{ get; set; }
public string FirstName
{ get; set; }
public string LastName
{ get; set; }
public string Password
{ get; set; }
public string City
{ get; set; }
}
view from which u will call it
<table class="table">
<tr>
<th>
FirstName
</th>
<th>
LastName
</th>
<th>
City
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td> #item.FirstName </td>
<td> #item.LastName </td>
<td> #item.City</td>
<td>
Edit |
Details |
Delete
</td>
</tr>
}
</table>
i hope this will be easy for u to understand
You can do something like this in your click or celldoubleclick event of your grid(if you used one)
if(dgEmp.CurrentRow.Index != -1)
{
employ.Id = (Int32)dgEmp.CurrentRow.Cells["Id"].Value;
//Some other stuff here
}
Then do something like this in your Delete Button:
using(Context context = new Context())
{
var entry = context.Entry(employ);
if(entry.State == EntityState.Detached)
{
//Attached it since the record is already being tracked
context.Employee.Attach(employ);
}
//Use Remove method to remove it virtually from the memory
context.Employee.Remove(employ);
//Finally, execute SaveChanges method to finalized the delete command
//to the actual table
context.SaveChanges();
//Some stuff here
}
Alternatively, you can use a LINQ Query instead of using LINQ To Entities Query:
var query = (from emp in db.Employee
where emp.Id == employ.Id
select emp).Single();
employ.Id is used as filtering parameter which was already passed from the CellDoubleClick Event of your DataGridView.
Here's a safe way:
using (var transitron = ctx.Database.BeginTransaction())
{
try
{
var employer = new Employ { Id = 1 };
ctx.Entry(employer).State = EntityState.Deleted;
ctx.SaveChanges();
transitron.Commit();
}
catch (Exception ex)
{
transitron.Rollback();
//capture exception like: entity does not exist, Id property does not exist, etc...
}
}
Here you can pile up all the changes you want, so you can do a series of deletion before the SaveChanges and Commit, so they will be applied only if they are all successful.
The best way is to check and then delete
if (ctx.Employ.Any(r=>r.Id == entity.Id))
{
Employ rec = new Employ() { Id = entity.Id };
ctx.Entry(rec).State = EntityState.Deleted;
ctx.SaveChanges();
}
For a generic DAO this worked:
public void Delete(T entity)
{
db.Entry(entity).State = EntityState.Deleted;
db.SaveChanges();
}

Simple update with Entity Framework

I have the following code and I cannot achieve saving the changes.
The parameter of my method is a string containing the RefCode of a product I want to modify in the database, then the query is pulling the BaseProduct that is supposed to be modified.
(I tried to simplify the code and set it in English, so I have probably introduced some syntactic errors, but in my code in debug mode, I get all the info FROM the DB). Is there something wrong with the "select new" in the Linq query ?
public static void UpdateProduct(ViewProduct productToUpdate)
{
using (var context = new my_Entities())
{
var BaseProduct = (from prod in context.Product
where prod.Ref == productToUpdate.BaseProduct.RefPrd
select new ViewBaseProduct
{
RefPrd = prod.Ref,
DescrPrd = prod.DescrPrd,
NormeCe = (bool)prod.NormeCE
}).FirstOrDefault();
if (BaseProduct != null)
{
//BaseProduct.NormeCe = false;
BaseProduct = productToUpdate.BaseProduct;
context.SaveChanges();
}
}
}
But BaseProduct is a ViewBaseProduct object, is ViewBaseProduct a entity class? It seems it is a ViewModel class.
You have to get de Product entity, modify his fields and savechanges. It seems you only apply changes to the ViewModel class.
Try this:
public static void UpdateProduct(ViewProduct productToUpdate)
{
using (var context = new my_Entities())
{
var BaseProduct = (from prod in context.Product
where prod.Ref == productToUpdate.BaseProduct.RefPrd)
.FirstOrDefault();
if (BaseProduct != null)
{
//BaseProduct.NormeCe = false;
BaseProduct.field1 = productToUpdate.BaseProduct.field1;
BaseProduct.field2 = productToUpdate.BaseProduct.field2;
//update the necesary fields
//......
context.SaveChanges();
}
}
}
This won't work that way. You should use the CurrentValues.SetValues() method:
contexte.Entry(BaseProduct).CurrentValues.SetValues(productToUpdate.BaseProduct);
I think you have to Try this
public static void UpdateProduct(ViewProduct productToUpdate)
{
using (var contexte = new my_Entities())
{
var BaseProduct = (from prod in contexte.Product
where prod.Ref == productToUpdate.BaseProduct.RefPrd
select new ViewBaseProduct
{
RefPrd = prod.Ref,
DescrPrd = prod.DescrPrd,
NormeCe = (bool)prod.NormeCE
}).FirstOrDefault();
if (BaseProduct != null)
{
BaseProduct.BaseProduct.RefPrd=productToUpdate.BaseProduct.RefPrd
BaseProduct.BaseProduct.DescrPrd=productToUpdate.BaseProduct.DescrPrd
BaseProduct.BaseProduct.NormeCE==(bool)productToUpdate.BaseProduct.NormeCE
contexte.SaveChanges();
}
}
}

Entity Framework 4. Join Tables. Entity Framework perfoming an Insert when no action is required

I have a database setup with the following: -
Person Table
Hobby Table
Game Table
GameInfo Table
Person [1 - M] Hobby [1 - M] Game [M - 1] GameInfo
Game is just a join from Hobby to GameInfo
I am having an issue whereby I would fetch Person which has a Collection<Game> and add to this collection (i.e. I am just updating links, not wanting to insert new GameInfo).
Upon Calling SaveChanges() EntityFramework will insert the links AS WELL AS inserting new GameInfo, which is not my desired result.
I have looked at Entry().State etc but the problem is where I am handling the updates of my Person is outside of the context.
I am basically fetching a Person creating a new Game with Ids that I know exist already and then calling the SaveChanges() and would expect that it would just insert into the Game Table, not the GameInfo table
EDIT 1: Code Sample - sort of
public void Save(Profile profile)
{
using (GDContext context = GetContext())
{
DataProfile dataProfile = context.Profiles.Single(u => u.ProfileId == profile.Id);
ProfileHandler.HandleDataModelChanges(dataProfile, profile);
context.SaveChanges();
}
}
public override void HandleDataModelChanges(DataProfile dataModel, Profile model)
{
dataModel.ProfileId = model.Id;
dataModel.FirstName = model.FirstName;
dataModel.LastName = model.LastName;
dataModel.DateOfBirth = model.DateOfBirth;
dataModel.Email = model.Email;
foreach(var hobby in model.Hobbies)
{
DataHobby dataHobby = dataModel.Hobbies.SingleOrDefault(p => p.HobbyId == hobby.HobbyId);
if (dataHobby == null)
{
dataHobby = new DataHobby();
}
HobbyHandler.HandleDataModelChanges(dataHobby, hobby);
}
}
public override void HandleDataModelChanges(DataHobby dataModel, Hobby model)
{
dataModel.HobbyId = model.Id;
HandleGames(dataModel, model);
HandleCrafts(dataModel, model);
HandleCollections(dataModel, model);
}
private void HandleGames(DataHobby dataModel, Hobby model)
{
IEnumerable<DataGame> gamesToRemove = dataModel.Games.Where(g => !model.Games.Any(ds => ds.Id == g.GameId)).ToArray();
foreach (var game in gamesToRemove)
{
dataModel.Games.Remove(game);
}
foreach (var game in model.Games)
{
if (!dataModel.Games.Any(e => e.GameId == game.Id))
{
DataGame dataGame = new DataGame();
dataGame.GameId = game.Id;
dataGame.GameName = game.Name;
dataModel.Games.Add(dataGame);
}
}
}
EDIT 2 - Context configuration
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.AutoDetectChangesEnabled = true;
public GameInfoConfiguration()
{
HasKey(x => x.GameId);
ToTable("GameData");
}
public PersonConfiguration()
{
HasKey(x => x.PersonId);
ToTable("Person");
}
public HobbyConfiguration()
{
HasKey(x => x.HobbyId);
HasRequired(x => x.Person).WithMany(x => x.Hobbies);
HasMany(x => x.Games).WithMany(g => g.Hobbies).Map(x => x.MapLeftKey("HobbieId").MapRightKey("GameId").ToTable("PersonGame"));
ToTable("HobbyGame");
}
Well I still don't see where you work with GameInfo - your description absolutely doesn't correspond with your code. By looking at your code I guess the problem will be in the snippet like this:
foreach (var game in model.Games)
{
if (!dataModel.Games.Any(e => e.GameId == game.Id))
{
DataGame dataGame = new DataGame();
dataGame.GameId = game.Id;
dataGame.GameName = game.Name;
dataModel.Games.Add(dataGame);
}
}
This will always insert a new Game - you told EF to insert a new Game. If you want to add existing Game you must do:
foreach (var game in model.Games)
{
if (!dataModel.Games.Any(e => e.GameId == game.Id))
{
DataGame dataGame = new DataGame();
dataGame.GameId = game.Id;
dataGame.GameName = game.Name;
context.Games.Attach(dataGame); // Now the context knows that it is not a new entity
dataModel.Games.Add(dataGame);
}
}
I think the mistake I have made here is that I am dealing with DataGame when really what I should be dealing with is a POCO class to represent the "join" between DataGame and Hobby Like a HobbyGame POCO.

Categories