Retrieving an object from one session and updating in another session nHibernate - c#

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?

Related

C# - NHibernate: evicting an entity and then getting it

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

Create: An entity object cannot be referenced by multiple instances of IEntityChangeTracker

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

Understanding the exception "An entity object cannot be referenced by multiple instances of IEntityChangeTracker."

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.

should EF dbContext be created on every transaction

I'm trying to figure out the best way to manage the DbContext. I've seen code samples that don't dispose and I've seen people say that that is a bad idea. Is it appropriate for me to do something like below? Also, should I put every transaction, including reads, in a new DbContext? This might be another question, but is the part about the EntityState necessary?
public abstract class GenericRepository<T> where T : EntityData
{
protected MyDbContext Context
{
get { return new MyDbContext(); }
}
public T Save(T obj)
{
T item;
using (var context = Context)
{
var set = context.Set<T>();
if (String.IsNullOrEmpty(obj.Id))
item = set.Add(obj);
else
{
item = set.Find(obj.Id);
item = obj;
}
// taken from another code sample
var entry = context.Entry(item);
if (entry.State == EntityState.Detached)
{
//Need to set modified so any detached entities are updated
// otherwise they won't be sent across to the db.
// Since it would've been outside the context, change tracking
//wouldn't have occurred anyways so we have no idea about its state - save it!
set.Attach(item);
context.Entry(item).State = EntityState.Modified;
}
context.SaveChanges();
}
return item;
}
}
EDIT
I also have an extended class that implements this function below. The context is not being wrapped in a using statement in this query, so I'm a little suspicious of my code.
public IQueryable<T> FindByAccountId(string accountId)
{
return from item in Context.Set<T>()
let user = UserRepository.FindByAccountId(accountId).FirstOrDefault()
where item.UserId == user.Id
select item;
}
Contexts should really be on a per request basis. The request comes in and a new context is created. This context is used for the remainder of the request then disposed of at the end of the request accordingly. This gives you the benefit of request long transactions, and as highlighted by HamidP, you also have the added benefit of cached entities; meaning that any entities loaded into the context can be loaded by retrieved without Entity Framework needing to query the database.
If you're using any kind of inversion of control container such as StructureMap then you can easily create HTTP request bound contexts by a configuration such as:
this.For<DbContext>().HybridHttpOrThreadLocalScoped().Use<DbContext>();
You're then able to inject your DbContext (or a derivative of it) into your repository and leave your IOC container of choice to dispose of the context at the end of the request. If you were to inject the same context into another repository then you'd receive the same instance of the context.
I hope this helps!
No, it should not
Best approach here is to assign a context just for a request. you should attach a context to an incoming request and dispose your context when request is finished. In this approach you save the overhead of creating a context for every transaction and also benefit from caching mechanism of context because each context has it's inside cache and a request may access the data it had access recently.
Creating a context for each transaction is not as bad as having a long life context!! Don't ever do that, long life contexts result in many concurrency issue and the cache becomes stale and memory consumption grows high and higher and you should maintain your application in future by miracles.

Entity Framework Code First Transaction

I am using Entity Framework 4 with SqlServer CE as the database.
Because SqlServer CE does not support TransactionScope, so I have to resort to using BeginTransaction and RollbackTransaction method.
I have two GRUD functions to delete and create rows in the database.
What I want to achieve is something like this :
using (var context = new MyContext())
{
using (var tx = context.BeginTransaction())
{
// grud functions
deleteRows();
addRows();
// do db stuff here...
tx.Commit();
}
}
But I cannot find BeginTransaction in the context.
How can I go about it?
How can I go about it
I think, you're misunderstanding a concept of DbContext. It is a local cache of entities + change tracker. Whatever you do with entities, this changes are just tracked by context without affecting underlying data source.
This all happens until you call SaveChanges method. This method applies changes from change tracker to data source in transaction manner, so, all of the changes you've made, will be consistent.
If you will write your deleteRows and addRows in a way, that they won't call SaveChanges, and put SaveChanges call somewhere outside these methods, you'll get desired transaction:
using (var context = new MyContext())
{
// grud functions
deleteRows(context);
addRows(context);
context.SaveChanges();
}
private void deleteRows(MyContext context) {}
private void addRows(MyContext context) {}
In ObjectContext, BeginTransaction() is a method on Connection, not on the ObjectContext
However, DbContext exposes BeginTransaction directly to the underlying connection. So possibly you aren't using a DbContext?
Try
context.Connection.BeginTransaction()

Categories