I have created a page asp.net MVC for inserting records to database where i am giving a preview button to see how the data will looks on client side before saving it. I am using session to pass model to the preview page. On preview page i have created a button which will save the model in session to database but it is throwing exception "An entity object cannot be referenced by multiple instances of IEntityChangeTracker".
I am using the same dbContext. I had tried many solutions given by users but they are not working for me. I have attached the part of the code that's throwing exception. Please see where I am doing wrong.
Here is the code where I am saving record
var model = Session[Constants.SessionVariables.ProjectModelForPreview] as Project;
if (create != null)
{
if (model.Id == 0)
{
if (model.IsFeatured)
{
foreach (var item in dbContext.Projects.Where(p => p.IsFeatured == true))
{
item.IsFeatured = false;
}
dbContext.SaveChanges();
}
dbContext.Entry(model).State = EntityState.Unchanged;
dbContext.SaveChanges();
TempData["SuccessMessage"] = "Project created successfully.";
return RedirectToAction("Index");
}
}
Your controller, and therefore your DbContext, is instantiated per request. So your application follows this flow:
Request 1 instantiates DbContext 1.
You load an entity through DbContext 1. This entity is being tracked, and the entity itself holds a reference to DbContext 1.
You store this entity in the session, keeping the old DbContext alive. This works, because the default session state works InProc, and not by serialization.
Request 2 comes in, DbContext 2 gets instantiated.
The entity is retrieved from the session.
You try to save the entity, still being tracked by DbContext 1, through DbContext 2. This throws.
Now for the solution there are various approaches:
Don't save entities in the session at all. Persist them, and look them up again in successive requests.
Save entities in the session by manually serializing them.
Load the entity with .AsNoTracking().
Let's ignore the original problem for now,it will be solved once you refactor the code
1)if for some reason you are using the same context across request ,stop.
(I don't think you do though).
2)Don't save tracked entities in the Session*
Search on google to see how EF tracks changes.
3)Read 1 and 2 again
*Use .AsNoTracking() or project your entity in a new model and save that in the session
Related
I'm trying to understand how NHibernate works. To do so I've created a small test, given below. But the test is failing on the marked line and I don't understand why.
What am I misunderstanding?
To briefly explain the code chunk... I create an entity in the DB. Then I call Evict to remove the entity from session cache so that the next call for it would force a DB read. Then I do a DB read, but instead of getting back an entity instance read from DB, I get NULL, on the marked line.
using NHibernate;
using MyCorp.MyProject.Resources.MyEntity;
using MyCorp.MyProjectTests.Common.Fixture;
using Xunit;
namespace MyCorp.MyProjectTests.Common.DB
{
[Collection("Component")]
public class NHibernateTest
{
private readonly ISessionFactory dbSessionFactory;
public NHibernateTest(ComponentFixture componentFixture)
{
this.dbSessionFactory = componentFixture.DatabaseFixture.DBSessionFactory;
}
[Fact]
[Trait("Category", "Component")]
public void TestSessionCache()
{
const string QUERY = #"DELETE MyEntityModel mg WHERE mg.Id = :id";
const string TITLE = "NHibernate session test object";
using (ISession dbSession = this.dbSessionFactory.OpenSession())
{
// Create new entity and then remove it from session cache.
long id = (long) dbSession.Save(new MyEntityModel
{
Title = TITLE
});
dbSession.Evict(dbSession.Get<MyEntityModel>(id));
// Entity loaded from DB and stored into session cache.
Assert.Equal(TITLE, dbSession.Get<MyEntityModel>(id).Title); // ===== FAILS HERE =====
// Delete entity from DB, but don't evict from session cache yet.
dbSession.CreateQuery(QUERY).SetParameter("id", id).ExecuteUpdate();
// Entity still reachable through session cache.
Assert.Equal(TITLE, dbSession.Get<MyEntityModel>(id).Title);
// Evict deleted entity from session cache.
dbSession.Evict(dbSession.Get<MyEntityModel>(id));
// Entity not available in neither DB nor session cache.
Assert.Null(dbSession.Get<MyEntityModel>(id));
}
}
}
}
Save() is not equal to SQL INSERT.
Save() means: make the session aware of this object and have the session send it to the database at a suitable time. Depending on mappings and configuration, this can be before Save() returns, or not.
So you evict the object from the session before it gets persisted.
If you omit the call to Evict(), your test works because none of the other code actually depends on the item being in the database (the DELETE statement may indicate it found 0 rows to delete, but this is not a problem for the test).
To use automatic flush behaviour, you should always be inside a transaction, not just a session. In fact, for best reliability, you really should always be inside a transaction whenever you are inside a session (other patterns are possible, but tend to be more complicated to get right).
Here is the documentation on when flushing of changes to the database happens:
http://nhibernate.info/doc/nhibernate-reference/manipulatingdata.html#manipulatingdata-flushing
I am creating an Application to manage Projects, Employees and assign Employees to Projects using WPF and EF 6.2.0.
So I've got an Employee entity with different properties (some of which are references to other entities), and I got a Project entity with different propeties (again, some of which are other entities).
This is my database scheme:
Now I want to create a Job entity which is assigned to an Employee and a Project.
So to add one, I have to attach the whole Employee and Project entities (including all their referenced entities) to my context before I can add the Job right?
If I don't do that or forget one of the entity's relations/properties, it's trying to create that whole entity on the database, and throws some error like the primary key for that employee already exists (obviously, because it shouldn't create it but rather attach the existing local one)
On the other side if I do try to attach it, it fails upon attaching the Project because (now comes the crazy part) the Project.CostCentre.Location.CostCentres[0].Projects[0].Contact (Employee) is already attached. It is trying to attach every single thing recursively including all Lists?!
That's the stack trace that causes this:
private void Attach(Job job, bool attachEntity = true)
{
Attach(job.Employee);
Attach(job.Project); // Calls next func
if (attachEntity && !Attached(job))
Context.Jobs.Attach(job);
}
private void Attach(Project project, bool attachEntity = true)
{
Attach(project.Contact);
Attach(project.CostCentre); // Calls next func
Attach(project.CostType);
Attach(project.Status);
if (attachEntity && !Attached(project))
Context.Projects.Attach(project);
}
private void Attach(CostCentre costCentre, bool attachEntity = true)
{
Attach(costCentre.Location); // Calls next func
if (attachEntity && !Attached(costCentre))
Context.CostCentres.Attach(costCentre);
}
private void Attach(Location location, bool attachEntity = true)
{
if (attachEntity && !Attached(location))
Context.Locations.Attach(location); // The error (that code tries to attach location.CostCentres[0].Projects[0].Contact aswell, which is an already attached Employee!!)
}
The full error message:
System.InvalidOperationException: 'Attaching an entity of type 'ForecastLibrary.Employee' 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.'
The full source code is here: https://pastebin.com/8YU0xQUC
I am already trying to solve this obscure error for some weeks, I hope someone here can help me out successfully adding this Job.
EDIT: Here's the code calling all of this:
// DataService::Add Function
public void Add(Job job)
{
Attach(job, false);
Context.Jobs.Add(job);
}
// UI Add job snippet
using (var service = new DataService(Session.ConnectionString))
{
service.Add(JobToAdd);
await service.SaveAsync();
}
I'd recommend using Eager loading if you know exactly what you need and have bigger data models.
Call Context.Configuration.LazyLoadingEnabled = false; in the constructor of you DataService and use .Include wherever you need a navigation property.
Then you need to use Find instead of Attach.
So your Job Add function could look this:
public void Add(Job job)
{
var employee = Context.Employees.Find(job.Employee.Id);
var project = Context.Projects.Find(job.Project.Id);
Context.Jobs.Add(new Job { Employee = employee, Project = project, Time = job.Time });
}
I need to update customer details. For this, I have to retrieve the entity from another session from repository and in the service, I am updating that entity. When I do this, I get an error message saying:
The operation is not valid for the current state of the enlistment.
But if I updated the entity without retrieving it from database, everything works fine.
This is how I am trying to update in Service.
Customer customer = customerService.getById(customer_payment.customer_id);
customer.deductBalance(customer_payment.discount_received);
customerService.updateCustomerDetails(customer);
This is my repository that updates the entity:
using (ISession session = SessionFactory.OpenSession)
{
using(ITransaction t = session.BeginTransaction())
{
session.SaveOrUpdate(customer);
t.Commit();
}
}
This is my function that returns the entity of the given ID:
Customer customer;
using (ISession session = SessionFactory.OpenSession)
{
customer = session.Get<Customer>(customer_id);
}
return customer;
How can I solve this problem? Thanks in advance.
Edit 1: This is what my OpenSession does:
Configuration configuration = new Configuration().Configure("hibernate.cfg.xml");
Assembly assembly = Assembly.GetCallingAssembly();
configuration.AddAssembly(assembly);
iSessionFactory = configuration.BuildSessionFactory();
CurrentSessionContext.Bind(iSessionFactory.OpenSession());
return iSessionFactory.OpenSession();
Is it a good approach to open a new session everytime or should I use Singleton pattern in SessionFactory?
Detach the customer from first ISession before updating it with other. You have to expose Evict method on repository OR have to expose ISession outside your repository.
Customer customer = customerService.getById(customer_payment.customer_id);
customerService.Evict(customer);
//OR
customerRepository.Evict(customer);
//OR
customerService.Session.Evict(customer);
//OR something similar...
customer.deductBalance(customer_payment.discount_received);
customerService.updateCustomerDetails(customer);
Refer following:
https://ayende.com/blog/4282/nhibernate-cross-session-operations
What does NHibernate Session.Evict do?
Can I detach an object from an NHibernate session?
Edit (for your Update)
Is it a good approach to open a new session everytime or should I use Singleton pattern in SessionFactory?
This is opinion based question actually; but it is recommended that your ISession should be short lived.
That said, you can create new session for each database action. But by doing this, you are missing many ORM features like Session Level Cache, Lazy Loading, Change Tracking (UoW) etc.
You can choose to move your UoW on Request level (i.e. ISession per Request) where you can avail the benefit of ORM features; but again there are other issues associated with it. Refer following: https://stackoverflow.com/a/48092471/5779732
Should I transform Entity (Persistent) objects to DTO objects?
I am doing a project where I am supposed to make an eBook store. This is an entity relationship model using which I generated a DB in SQL Server.
Now, while generating the bill using the following code, I am getting the An entity object cannot be referenced by multiple instances of IEntityChangeTracker. exception in while calling the SaveChanges() method for my Entity Relationship model, (ebs)
Here is the code.
I am maintaining the cart in the session. Also, the user id is kept in the session too.
List<Title> cartItems = (List<Title>)Session["eStoreCart"];
int userid = Int32.Parse(Session["eStoreUserId"].ToString());
User us = ebs.Users.SingleOrDefault(u => u.UserId == userid);
Bill bl = new Bill();
bl.BillAmount = Decimal.Parse(lblBill.Text);
bl.BillDate = DateTime.Now;
foreach (Title item in cartItems)
{
bl.Titles.Add(item);
}
us.Bills.Add(bl);
ebs.SaveChanges();
Response.Redirect("Orders.aspx");
I am totally new to Entity Framework and LINQ. So any help explaining what is going on, and a workaround will be appreciated.
It looks like you may be creating your DbContext ebs as an instance variable and keeping it around. You should instead consider your entity contexts lightweight and create them on demand, i.e. each time you need to query or modify the data store, especially since using this in a web application.
Please review these docs: Working with DbContext and Add/Attach and Entity States.
Specifically, note this:
When working with Web applications, use a context instance per
request.
So don't keep a long-running DbContext around: instead create a new one in a using block when you want to modify your data store. DbContext is pretty lightweight and so you can build them and tear them down like this on demand.
See also this answer.
Now, as for the exception: I expect that it's due to the Title objects that you're storing in the session: those must have been created in another DbContext. You can try to attach them to the new DbContext (review the doc linked above). But another approach: don't keep the Title objects in the session, instead keep IDs for those objects, then look them up again in the new DbContext.
Something like this:
In your case, the code might look something like this:
List<int> cartItemIds = (List<int>)Session["eStoreCart"];
int userid = Int32.Parse(Session["eStoreUserId"].ToString());
using (var ebs = new MyDbContext())
{
User us = ebs.Users.SingleOrDefault(u => u.UserId == userid);
Title title = null;
Bill bl = new Bill();
bl.BillAmount = Decimal.Parse(lblBill.Text);
bl.BillDate = DateTime.Now;
foreach (var id in cartItems)
{
title = ebs.Titles.Where(t => t.Id == id);
bl.Titles.Add(title);
}
us.Bills.Add(bl);
ebs.SaveChanges();
}
Response.Redirect("Orders.aspx");
Another tip: look into the ASP Membership API for some additional webapp user support. You may not need to change much, and you'll get some API for doing things like password policies/expiration/changing.
I have a User table, with a many-to-many relationship to an Alerts table. After creating a Membership user, I am adding some extra info into the database.
MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email);
if (createStatus == MembershipCreateStatus.Success) {
User user = new MidTier.Models.User();
user.FullName = model.FullName;
if (Alerts.Count() > 0)
{
var userAlerts = SetAlert(Alerts); // creates an IEnumerable of Alerts (from a list of int )
foreach (var alert in userAlerts)
{
user.Alerts.Add(alert); //add each alert to the user
}
}
userRepository.Add(user); //throwing error
userRepository.Save();
}
I get an error (' An entity object cannot be referenced by multiple instances of IEntityChangeTracker.') on calling the Add method. there are lots of references about this error on the net even here on SO, but after reading all those comments and suggestions, I a havent found a solution or the reason I am getting this error.
there are lots of references about
this error on the net even here on SO,
but after reading all those comments
and suggestions, I a havent found a
solution or the reason I am getting
this error.
If you really searched you should already know that error says you that some entity in object graph is already attached to other context. Because of that your code sample is mostly not related. The real important code is wrapped in your methods - probably SetAlerts and userRepository.Add. If these two methods use internally context and they don't use the same instance it is the reason for your exception.