Working on a project using Entity Framework (4.3.1.0). I'm trying to figure out how to make my code work as a transaction, but for me it seems like my model doesnt update after the transaction has failed.
Let me show you:
using (TransactionScope trans = new TransactionScope())
{
_database.Units.Add(new Unit{ ... });
var a = false;
if (a)
{
trans.Complete();
Refresh();
}
}
Refresh();
What I experience is that after the transactionscope is finished it doesnt roll back to it's previous state. When I run the refresh method I iterate over all the items in Units and insert the values into a ObservableCollection which I display on the screen in a WPF window.
This mechanism works for when I successfully perform the transaction, but when I run the code above, the grid updates with the newly added Unit, but it does not go away after I run Refresh after the transaction.
I have the feeling I'm doing something fundamentaly wrong here :)
Entity Framework does not support transactions for the in-memory tracked entities - its "ObjectStateManager" which you see in the ObjectContext is not a transactional resource. The TransactionScope only "applies" to the database operations (queries, updates) done within it, not the in-memory operations, such as manipulating the object graph (which is what you do).
Related
I am chasing an issue with MySql / EF Core where I randomly have an exception thrown saying Nested transactions are not supported. This exception does not occur when my system is only used by one user. But when I run my tests in parallel or when I have multiple users, the exception occurs. I looked at all the code and their is nothing that could create nested transactions.
The only piece of codes that scares me so far is something like the following:
using (var transaction = _context.Database.BeginTransaction())
{
// Create a few entities and add them to the EF context
...
// Insert the rows: hopefully at this point, my transaction is not commited yet.
_context.SaveChanges();
// I then want to update a few rows with a raw sql statement without
// having to load the entities in memory.
_context.Database.ExecuteSqlCommand("UPDATE ...");
// Now that I have inserted and inserted some data, I want to commit all these
// changes atomically.
transaction.Commit();
}
Is this safe? Am I guaranteed that my SaveChanges and ExecuteSqlCommand will be executed on the same MySqlConnection? I have the feeling that when I call SaveChanges, it closes my connection and puts it back on the connection pool. Then, my ExecuteSqlCommand takes a new connection from the pool (it may be the same one or another one). So my initial connection (the one where I opened the transaction) is put back in the pool and it could be reused by another thread.
This is just a hunch and I am totally not sure if this could cause the problem.
My real question in the end is:
is it safe to use SaveChanges and ExecuteSqlCommand within a transaction?
I upgraded from MySql.Data.EntityFrameworkCore/MySql.Data 6.10.1-beta to 6.10.3-rc and it looks like the problem is gone. Since the problem was random I can't be totally sure that the problem is indeed fixed, but so far so good.
EDIT:
3 years later, the problem was never observed anymore.
Entity Framework 6 introduced a new way to support transactions in the DbContext with the BeginTransaction method:
var db = new MyDbContext();
using(var tx = db.Database.BeginTransaction())
{
// update entities
try
{
db.SaveChanges();
tx.Commit();
}
catch(Exception)
{
tx.Rollback();
}
}
Is the Rollback() call in the method necessary? What happens if it is not called on an exception? I know when using TransactionScope it will roll back the transaction automatically when it is disposed and Complete is not called. Does DbContextTransaction behave similarly?
No it is not necessary to explicitly call Rollback. The tx variable will be disposed when the using block finishes, and the transaction will be rolled-back if Commit() has not been called.
I have tested this using SQL Server Activity Monitor, by observing the locks held on the database objects, as well as querying against the database to observe when the data is rolled back, using the nolock hint in my select statement to be able to view uncommitted changes in the database.
E.g. select top 10 * from [tablename] (nolock) order by modifiedDate
To the EF, the database provider is arbitrary and pluggable and the
provider can be replaced with MySQL or any other database that has an
EF provider implementation. Therefore, from the EF point of view,
there is no guarantee that the provider will automatically rollback
the disposed transaction, because the EF does not know about the
implementation of the database provider.
This answer pretty much explains everything and confusion with all msdn docs having that Rollback called explicitly: https://stackoverflow.com/a/28915506/5867244
I have a WPF app that has a grid with a list of data that I loaded with EF. Some other window can make changes to the same data loaded on the grid but using a different dbcontext instance. How can I see the changed data on the grid? I know I can refresh a single entity with ctx.Entry<MyEntity>(instance).Reload(); - but I want to see all the changes and no matter what I do, I only see the old values. I can't use AsNoTracking neither create a new DbContext instance in this case.
To me looks like a very simple case and I cannot see why EF don't just
update the values of the entities.
EF has this mechanism as well but it is not exposed on DbContext API. You need to get back to ObjectContext. If you just want to reload set of entities you will call:
var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, listOfEntitiesToReload);
RefreshMode.StoreWins causes all pending changes to be overwritten by reloaded values. You can also use RefreshMode.ClientWins which will keep your changes and merge them with reloaded data. The problem with this approach is that it only reloads entities you already have. You will not get new entities.
If you want to get new entities as well you must execute a query and you must tell EF that you want to reload values:
var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
var objectSet = objectContext.CreateObjectSet<MyEntity>();
objectSet.MergeOption = MergeOption.OverwriteChanges;
var result = objectSet.Where(...).ToList();
Again MergeOption.OverwriteChanges overwrites all pending changes but you can use MergeOption.PreserveChanges to merge reloaded values to your edited values.
I think there can be still some issues with refreshing values with some relations and maybe also entities which were deleted in the database.
I have a named query that returns a Collection of entities.
These entities have a #PreUpdate-annotated method on them. This method is invoked during query.getResultList(). Because of this, the entity is changed within the persistence context, which means that upon transaction commit, the entity is written back to the database.
Why is this? The JPA 2.0 specification does not mention explicitly that #PreUpdate should be called by query execution.
The specification says:
The PreUpdate and PostUpdate callbacks occur before and after the
database update operations to entity data respectively. These database
operations may occur at the time the entity state is updated or they
may occur at the time state is flushed to the database (which may be
at the end of the transaction).
In this case calling query.getResultList() triggers a em.flush() so that the query can include changed from current EntityManager session. em.flush() pushes all the changes to the database (makes all UPDATE,INSERT calls). Before UPDATE is sent via JDBC #PreUpdate corresponding hooks are called.
This is just my comment from rzymek's answer with some follow up code:
I tried to reproduce the problem OP had, because it sounded like the EntityManager would get flushed everytime the query is called. But that's not the case. #PostUpdate methods are only called when there is actual changed being done to the Database as far as I can tell. If you made a change with the EntityManager that is not yet flushed to the DB query.getResultList will trigger the flush to the DB which is the behaviour one should expect.
Place valinorDb = em.find(Place.class, valinorId);
// this should not trigger an PostUpdate and doesn't
// TODO: unit-testify this
em.merge(valinorDb);
valinorDb.setName("Valinor123");
valinorDb.setName("Valinor");
// this shouldn't trigger an PostUpdate because the Data is the same as in the beginning and doesn't
em.merge(valinorDb);
{
// this is done to test the behaviour of PostUpdate because of
// this:
// http://stackoverflow.com/questions/12097485/why-does-a-jpa-preupdate-annotated-method-get-called-during-a-query
//
// this was tested by hand, but should maybe changed into a unit
// test? PostUpdate will only get called when there is an actual
// change present (at least for Hibernate & EclipseLink) so we
// should be fine
// to use PostUpdate for automatically updating our index
// this doesn't trigger a flush as well as the merge didn't even trigger one
Place place = (Place) em.createQuery("SELECT a FROM Place a")
.getResultList().get(0);
Sorcerer newSorcerer = new Sorcerer();
newSorcerer.setName("Odalbort the Unknown");
place.getSorcerers().add(newSorcerer);
//this WILL trigger an PostUpdate as the underlying data actually has changed.
place = (Place) em.createQuery("SELECT a FROM Place a")
.getResultList().get(0);
}
In my case JPA Event Listener (#EntityListeners) calls query.getResultList() in its logic (to do some validation) and in effect goes into
neverending loop that calls the same listener once again and again and in the end got StackOverflowError. I used flush-mode = COMMIT to avoid flush on query like below. Maybe for someone it will be helpful.
List l = entityManager.createQuery(query)
/**
* to NOT do em.flush() on query that trigger
* #PreUpdate JPA listener
*/
.setFlushMode(FlushModeType.COMMIT)
.getResultList();
I'm trying to use Entity Framework to query database and I have following code that I'm using to get some data.
var students= MyEntities.Students.Where(s => s.Age > 20).ToList();
This code works fine and returns correct data. However, if I run this code, then go to database and update records to change the data this code should return, and then re-run this code without shutting down the app, I'm getting original data.
I'm pretty sure it used to work fine, but now this doesn't refresh data.
No it never worked. This is essential behavior of entity framework (and many ORM tools) called identity map (explanation here).
If you run the query on the same context you will not get your entities updated. It will only append new entities created in the database between running those two queries. If you want to force EF to reload entities you must do something like this:
ObjectQuery query = MyEntities.Students;
query.MergeOption = MergeOption.OverwriteChanges;
var students = query.Where(s => s.Age > 20).ToList();
You are running into issues because EF caches data, so if the data changes behind the scenes, and you dont dispose/reopen your context you are going to run into issues.
The general rule of thumb is to keep the lifetime of the context as short as possible, to circumvent issues like you just mentioned.
Please do not disregard what I said above, but if you would like to force a refresh from the DB you could use the Refresh() method.