I have a repository class that uses an NHibernate session to persist objects to the database. By default, the repository doesn't use an explicit transaction - that's up to the caller to manage. I have the following unit test to test my NHibernate plumbing:
[Test]
public void NHibernate_BaseRepositoryProvidesRequiredMethods()
{
using (var unitOfWork = UnitOfWork.Create())
{
// test the add method
TestRepo.Add(new TestObject() { Id = 1, Name = "Testerson" });
TestRepo.Add(new TestObject() { Id = 2, Name = "Testerson2" });
TestRepo.Add(new TestObject() { Id = 3, Name = "Testerson3" });
// test the getall method
var objects = TestRepo.GetAll();
Assert.AreEqual(3, objects.Length);
// test the remove method
TestRepo.Remove(objects[1]);
objects = TestRepo.GetAll();
Assert.AreEqual(2, objects.Length);
// test the get method
var obj = TestRepo.Get(objects[1].Id);
Assert.AreSame(objects[1], obj);
}
}
The problem is that the line
Assert.AreEqual(3, objects.Length);
fails the test because the object list returned from the GetAll method is empty. If I manually flush the session right after inserting the three objects, that part of the test passes. I'm using the default FlushMode on the session, and according to the documentation, it's supposed to flush before running the query to retrieve all the objects, but it's obviously not. What I am missing?
Edit: I'm using Sqlite for the unit test scenario, if that makes any difference.
You state that
according to the documentation, it's supposed to flush before running the query to retrieve all the objects
But the doc at https://www.hibernate.org/hib_docs/v3/api/org/hibernate/FlushMode.html, the doc states that in AUTO flush mode (emphasis is mine):
The Session is sometimes flushed
before query execution in order to
ensure that queries never return stale
state. This is the default flush mode.
So yes, you need to do a flush to save those values before expecting them to show up in your select.
Related
I was writing tests using SQLite in-memory database with XUnit and ASP.NET Core 3.1 and found strange behavior.
Lets say that we have User model and we want to change property IsActive to false:
var u = new User {Id = Guid.NewGuid(), IsActive = true};
_db.Users.Add(u);
_db.SaveChanges();
u.IsActive = false;
// Returns false
var isActive = _db.Users.Single(x => x.Id == u.Id).IsActive;
// Returns true
var isActiveNoTracking = _db.Users.AsNoTracking().Single(x => x.Id == u.Id).IsActive;
// Fails.
Assert.Equal(isActive, isActiveNoTracking);
I get different result depending if AsNoTracking() is called or not. Why is this happening? Isn't AsNoTracking() supposed to stop tracking changes made on fetched object, not to mess with data that was already changed?
If I call SaveChanges() after changing the property then it is all good (as expected):
var u = new User {Id = Guid.NewGuid(), IsActive = true};
_db.Users.Add(u);
_db.SaveChanges();
u.IsActive = false;
_db.SaveChanges();
// Returns false
var isActive = _db.Users.Single(x => x.Id == u.Id).IsActive;
// Returns false
var isActiveNoTracking = _db.Users.AsNoTracking().Single(x => x.Id == u.Id).IsActive;
// Success.
Assert.Equal(isActive, isActiveNoTracking);
So I am confused, I'm not sure when SQLite in-memory actually commits changes. Sometimes you can fetch changes from db without calling SaveChanges() but sometimes you cannot.
Here is code related to db
public class SqliteInMemoryAppDbContext : AppDbContext
{
public SqliteInMemoryAppDbContext(IConfiguration configuration) : base(configuration)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
options.UseSqlite(connection);
}
}
// I create db context for each test like this and dispose it after each test.
var _db = new SqliteInMemoryAppDbContext(null);
_db.Database.EnsureDeleted();
_db.Database.EnsureCreated();
So I am confused, I'm not sure when SQLite in-memory actually commits changes. Sometimes you can fetch changes from db without calling SaveChanges() but sometimes you cannot.
This is impossible. In order for something to be saved to the DB you need to call SaveChanges. What happens here is that you see local objects and you assume that they are stored in your DB. I generally suggest that you use a a DB query tool to learn how it works because it can be difficult at first.
Entity Framework has some local objects that it stores. For example at your first query.
// returns true, because it checks the db due to no tracking
_db.Users.AsNoTracking().Where(x => x.IsActive).OrderBy(x=>x.Username).ToList()[0].IsActive
// returns false, it finds the local reference
_db.Users.Where(x => x.IsActive).OrderBy(x=>x.Username).ToList()[0].IsActive
As you can see from the comments above it has different behavior based on the commands. It's not about when changes are saved to the db. This happens only if you call SaveChanges. What you are confused is for when the 'queries' you write with EF look at the DB or locally.
Generally for SQL at least I like to work with SQL profiler to see what queries EF sends to the Database. For example in your case you will have a query with where and order by send to the db.
EDIT:
About how to understand when the db is called or not i suggest reading here.
To summarize AsNoTracking always creates the new entity which means that it will look in the db for it. Instead the other commands in your example first look locally for the object.
I am investigating some unexpected behavior with NHibernate that needs more clarity.
I create a new object 'Request' and save it. I create another object 'AuditLog' and add request as a reference to AuditLog. I save that too.
Now, if the Request object is evicted from the session (for some reason), and updated again, the references in AuditLog is NULLified in the database when the transaction is committed.
Any ideas on why this would happen?
If the Request object is not created in the session, but retrieved from the database, and the same process runs, the reference in AuditLog is maintained.
Sample code which has been edited for ease in understanding.
If I remove the session.Evict(request1) from the code, the test passes. With this code, when the session closes, an additional query is fired on the DB to null the reference of request in AuditLog.
//Session 1
var session = Resolve<IFullSession>().Session();
using (var tx = session.BeginTransaction())
{
var request1 = new Request { Id = "REQ01" };
request1.SetFieldValue("Type", "Stage1"); //Type is column in Request table
session.Save("Request", request1);
var auditLog1 = new AuditLog { Id = "LOG01" };
auditLog1.SetFieldValue("Request", request1); //Request is reference column to AuditLog
session.Save("AuditLog", auditLog1);
session.Evict(request1);
request1.SetFieldValue("Type", "Stage2");
session.SaveOrUpdate("Request", request1);
tx.Commit();
}
CreateInnerContainers(); // This closes earlier session.
//Session 2
var session2 = Resolve<IFullSession>().Session();
using (var tx = session2.BeginTransaction())
{
var theLogObject = session2.Get<AuditLog>("LOG01");
Assert.IsNotNull(theLogObject); // This is true
Assert.IsNotNull(theLogObject.GetFieldValue("Request")); // This fails
tx.Commit();
}
You can access session objects and use then whatever you like
session.GetSessionImplementation().PersistenceContext.EntityEntries
but if I were you i would make sure that i'm evicting the right object and spend some time on debuging. Knowing what is going on is better than searching for workarounds
foreach (var e in session.GetSessionImplementation().PersistenceContext.EntityEntries.Values.OfType<EntityType>().Where(<condition>))
{
session.Evict(e);
}
The Save call on the session doesn't mean that your entity is written to the underlying database. It will become persisted in the current persistence context (your session).
If you now remove this entity from the persistence context (what you do with "evict"), your session will not be able to save it on flush (transaction end).
Try to call session.Flush() just before session.Evict(request1) and see what happens.
I do not know NHibernate, I'm coming from hibernate, but eventually this helps to clarify.
I'm new to NHibernate and was assigned to a task where I have to change a value of an entity property and then compare if this new value (cached) is different from the actual value stored on the DB. However, every attempt to retrieve this value from the DB resulted in the cached value. As I said, I'm new to NHibernate, maybe this is something easy to do and obviously could be done with plain ADO.NET, but the client demands that we use NHibernate for every access to the DB. In order to make things clearer, those were my "successful" attempts (ie, no errors):
1
DetachedCriteria criteria = DetachedCriteria.For<User>()
.SetProjection(Projections.Distinct(Projections.Property(UserField.JobLoad)))
.Add(Expression.Eq(UserField.Id, userid));
return GetByDetachedCriteria(criteria)[0].Id; //this is the value I want
2
var JobLoadId = DetachedCriteria.For<User>()
.SetProjection(Projections.Distinct(Projections.Property(UserField.JobLoad)))
.Add(Expression.Eq(UserField.Id, userid));
ICriteria criteria = JobLoadId.GetExecutableCriteria(NHibernateSession);
var ids = criteria.List();
return ((JobLoad)ids[0]).Id;
Hope I made myself clear, sometimes is hard to explain a problem when even you don't quite understand the underlying framework.
Edit: Of course, this is a method body.
Edit 2: I found out that it doesn't work properly for the method call is inside a transaction context. If I remove the transaction, it works fine, but I need it to be in this context.
I do that opening a new stateless session for geting the actual object in the database:
User databaseuser;
using (IStatelessSession session = SessionFactory.OpenStatelessSession())
{
databaseuser = db.get<User>("id");
}
//do your checks
Within a session, NHibernate will return the same object from its Level-1 Cache (aka Identity Map). If you need to see the current value in the database, you can open a new session and load the object in that session.
I would do it like this:
public class MyObject : Entity
{
private readonly string myField;
public string MyProperty
{
get { return myField; }
set
{
if (value != myField)
{
myField = value;
DoWhateverYouNeedToDoWhenItIsChanged();
}
}
}
}
googles nhforge
http://nhibernate.info/doc/howto/various/finding-dirty-properties-in-nhibernate.html
This may be able to help you.
In the dbml designer I've set Update Check to Never on all properties. But i still get an exception when doing Attach: "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported." This approach seems to have worked for others on here, but there must be something I've missed.
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
test.Name = "test2";
using(TheDataContext dc = new TheDataContext())
{
dc.Members.Attach(test, true);
dc.SubmitChanges();
}
The error message says exactly what is going wrong: You are trying to attach an object that has been loaded from another DataContext, in your case from another instance of the DataContext. Dont dispose your DataContext (at the end of the using statement it gets disposed) before you change values and submit the changes. This should work (all in one using statement). I just saw you want to attach the object again to the members collection, but it is already in there. No need to do that, this should work just as well:
using(TheDataContext dc = new TheDataContext())
{
var test = dc.Members.FirstOrDefault(m => m.fltId == 1);
test.Name = "test2";
dc.SubmitChanges();
}
Just change the value and submit the changes.
Latest Update:
(Removed all previous 3 updates)
My previous solution (removed it again from this post), found here is dangerous. I just read this on a MSDN article:
"Only call the Attach methods on new
or deserialized entities. The only way
for an entity to be detached from its
original data context is for it to be
serialized. If you try to attach an
undetached entity to a new data
context, and that entity still has
deferred loaders from its previous
data context, LINQ to SQL will thrown
an exception. An entity with deferred
loaders from two different data
contexts could cause unwanted results
when you perform insert, update, and
delete operations on that entity. For
more information about deferred
loaders, see Deferred versus Immediate
Loading (LINQ to SQL)."
Use this instead:
// Get the object the first time by some id
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
// Somewhere else in the program
test.Name = "test2";
// Again somewhere else
using(TheDataContext dc = new TheDataContext())
{
// Get the db row with the id of the 'test' object
Member modifiedMember = new Member()
{
Id = test.Id,
Name = test.Name,
Field2 = test.Field2,
Field3 = test.Field3,
Field4 = test.Field4
};
dc.Members.Attach(modifiedMember, true);
dc.SubmitChanges();
}
After having copied the object, all references are detached, and all event handlers (deferred loading from db) are not connected to the new object. Just the value fields are copied to the new object, that can now be savely attached to the members table. Additionally you do not have to query the db for a second time with this solution.
It is possible to attach entities from another datacontext.
The only thing that needs to be added to code in the first post is this:
dc.DeferredLoadingEnabled = false
But this is a drawback since deferred loading is very useful. I read somewhere on this page that another solution would be to set the Update Check on all properties to Never. This text says the same: http://complexitykills.blogspot.com/2008/03/disconnected-linq-to-sql-tips-part-1.html
But I can't get it to work even after setting the Update Check to Never.
This is a function in my Repository class which I use to update entities
protected void Attach(TEntity entity)
{
try
{
_dataContext.GetTable<TEntity>().Attach(entity);
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
catch (DuplicateKeyException ex) //Data context knows about this entity so just update values
{
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
}
Where TEntity is your DB Class and depending on you setup you might just want to do
_dataContext.Attach(entity);
I'm loading an instance twice from the same session, but nhibernate returns two instances which I am assuming means that the entity is not in the first level cache. What can cause this sort of behaviour?
Test:
using (new TransactionScope())
{
// arrange
NewSessionUnitOfWorkFactory factory = CreateUnitOfWorkFactory();
const int WorkItemId = 1;
const string OriginalDescription = "A";
WorkItemRepository repository = new WorkItemRepository(factory);
WorkItem workItem = WorkItem.Create(WorkItemId, OriginalDescription);
repository.Commit(workItem);
// act
using (IUnitOfWork uow = factory.Create())
{
workItem = repository.Get(WorkItemId);
WorkItem secondInstance = repository.Get(WorkItemId);
// assert
Assert.AreSame(workItem, secondInstance);
}
}
Update
The reason for this odd behaviour was this line of code:
NewSessionUnitOfWorkFactory factory = CreateUnitOfWorkFactory();
When I replaced it with this factory impl:
ExistingSessionAwareUnitOfWorkFactory factory = new ExistingSessionAwareUnitOfWorkFactory(CreateUnitOfWorkFactory(), new NonTransactionalChildUnitOfWorkFactory());
It works as expected.
I'm just guessing here, as you did not include the code for your Repository/UnitOfWork implementations. Reading this bit of code though, how does your Repository know which UnitOfWork it should be acting against?
First Level Cache is at the Session level, which I am assuming is held in your IUnitOfWork. The only setting on the Repository is the Factory, so my next assumption is that the code for repository.Get() is instantiating a new Session and loading the object through it. So the next call to Get() will instantiate another new Session and load the object. Two different level 1 caches, two different objects retrieved.
Of course, if your UnitOfWork is actually encapsulating Transaction, and the Factory is encapsulating Session, then this doesn't actually apply :)