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.
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.
This code always fails with a ConcurrencyException:
[Test]
public void EventOrderingCode_Fails_WithConcurrencyException()
{
Guid id = Guid.NewGuid();
using (var scope1 = new TransactionScope())
using (var session = DataAccess.NewOpenSession)
{
session.Advanced.UseOptimisticConcurrency = true;
session.Advanced.AllowNonAuthoritativeInformation = false;
var ent1 = new CTEntity
{
Id = id,
Name = "George"
};
using (var scope2 = new TransactionScope(TransactionScopeOption.RequiresNew))
{
session.Store(ent1);
session.SaveChanges();
scope2.Complete();
}
var ent2 = session.Load<CTEntity>(id);
ent2.Name = "Gina";
session.SaveChanges();
scope1.Complete();
}
}
It fails at the last session.SaveChanges. Stating that it is using a NonCurrent etag. If I use Required instead of RequiresNew for scope2 - i.e. using the same Transaction. It works.
Now, since I load the entity (ent2) it should be using the newest Etag unless this is some cached value attached to scope1 that I am using (but I have disabled Caching). So I do not understand why this fails.
I really need this setup. In the production code the outer TransactionScope is created by NServiceBus, and the inner is for controlling an aspect of event ordering. It cannot be the same Transaction.
And I need the optimistic concurrency too - if other threads uses the entity at the same time.
BTW: This is using Raven 2.0.3.0
Since no one else have answered, I had better give it a go myself.
It turns out this was a human error. Due to a bad configuration of our IOC container the DataAccess.NewOpenSession gave me the same Session all the time (across other tests). In other words Raven works as expected :)
Before I found out about this I also experimented with using TransactionScopeOption.Suppress instead of RequiresNew. That also worked. Then I just had to make sure that whatever I did in the suppressed scope could not fail. Which was a valid option in my case.
Within a single transaction scope, I am trying to save data to two separate Raven databases (on the same Raven server).
However, changes in the second session are not being saved to the server.
Here is the code I am using:
var documentStore = new DocumentStore { Url = "http://localhost:8081/" };
documentStore.Initialize();
documentStore.DatabaseCommands.EnsureDatabaseExists("database1");
documentStore.DatabaseCommands.EnsureDatabaseExists("database2");
using (var ts = new TransactionScope(TransactionScopeOption.RequiresNew))
{
using (var session1 = documentStore.OpenSession("database1"))
{
session1.Store(new Entity {Id = "1"});
session1.SaveChanges();
}
using (var session2 = documentStore.OpenSession("database2"))
{
session2.Store(new Entity {Id = "2"});
session2.SaveChanges();
}
ts.Complete();
}
I can see changes in session1 being saved to database1 but changes in session2 are not saved to database2. I've tried with RavenDb build 992 and build 2360
According to the Raven documentation, transactions across databases are supported.
How can I get changes from session2 to be committed?
The distributed transaction needs time to commit. You can either wait briefly with Thread.Sleep before checking your result, or you can set a special property in the session that is checking the result:
session.Advanced.AllowNonAuthoritativeInformation = false;
This seems like a bug, but it is actually by design. Read Working with System.Transactions in the RavenDB documentation.
(FYI - I didn't know this either until I checked.)
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 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.