With 2 different context classes which use the same database what is the best way include changes to both in the same transaction?
You can use TransactionScope for distributed transaction. Concept:
using(var transaction = new TransactionScope())
{
using (var context1 = new DbContext1())
{
...
context1.SaveChanges();
}
using (var context2 = new DbContext2())
{
...
context2.SaveChanges();
}
transaction.Complete();
}
Only when TransactionScope is completed (committed), changes will reflect in database.
Related
I have not had any luck with transactions and entity framework 5. I have the following code:
context.Database.Connection.Open();
transaction = context.Database.Connection.BeginTransaction(IsolationLevel.Serializable);
//some work happens
context.SaveChanges();
//some additional work
context.SaveChanges();
transaction.Commit();
At the very first context.SaveChanges call, I get an exception: "Connection is already part of a local or a distributed transaction"
Right now I am actually just doing a trivial proof of concept where all I am doing is attaching an entity, marking it as modified and then calling save changes.
As a troubleshooting deal, I put in an event handler for when the connection state changes and had a breakpoint in there. Doing that, I verified that the connection did not close on me between when I started the transaction and when I called save changes.
Any help figuring out why it is giving me that exception would be tremendously appreciated.
This is the way we used transactions before. It worked for us:
public void DoSomething()
{
using (var db = GetContext())
{
using (var ts = GetTransactionScope())
{
//do stuff
db.SaveChanges();
ts.Complete();
}
}
}
public TransactionScope GetTransactionScope()
{
var tso = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
return new TransactionScope(TransactionScopeOption.Required, tso);
}
If for some reason you have to do multiple SaveChanges calls in one transaction the recommended way is to wrap them in a TransactionScope:
using(var tran = new TransactionScope())
{
using(var context = new MyContext())
{
//some work happens
context.SaveChanges();
//some additional work
context.SaveChanges();
}
tran.Complete(); // without this call the transaction is rolled back.
}
The default isolation level is serializable. Each connection that is opened within the transaction enlists in this transaction. By default, EF always opens and closes connections when it executes queries.
I guess the cause of this exception you've got is that EF creates a transaction object itself when it executes SaveChanges. It tries to use its connection to start this transaction, but the connection is already part of the transaction you created. By using a TransactionScope, the EF transaction just enlists in the ambient transaction.
I'm new to EF and I've only worked with EF 6. When I use it to access the data from a database that I have already designed I don't see any method that allows me to manipulate data. To solve this temporarily I created some stored procedures for adding, deleting and updating data.
I would like to know if what I am doing is the right way to manipulate data in EF or not. In case it is not the right way how can I do this using the built in features of EF6. MSDN said there is an add object but couldn't find it.
There have been some changes in the API. EF6 does not use ObjectContext anymore, it uses a DbContext. This can be generated from a Database Model, or created using a Model first approach.
Old syntax:
objectContext.AddToUsers(user);
is now:
dbContext.Users.Add(user);
Here are some basic samples:
insert:
using(var dbContext = new MyDbContext())
{
var user = new User { ID=1, Name="Test" };
dbContext.Users.Add(user); // Add user
dbContext.SaveChanges(); // Save changes to DB
}
update:
using(var dbContext = new MyDbContext())
{
var user = dbContext.Users.Find(1);// find by ID = 1
user.Name = "New Name"; // Change name
dbContext.SaveChanges(); // Save changes to DB
}
delete:
using(var dbContext = new MyDbContext())
{
var user = dbContext.Users.Find(1);// find by ID = 1
dbContext.Users.Remove(user); // delete user
dbContext.SaveChanges(); // Save changes to DB
}
So, no need for stored procedures.. definetly not needed for simple CRUD.
We are building an application, that is using a legacy framework utilising ADO.NET. This framework manages its own connection to the DB for calls to its code API.
For any customisations and custom tables we are using Entity Framework and hence a separate connection to the DB is made.
The application and DB is to be hosted on Azure.
What we would like to do is wrap both calls to the legacy framework and to Entity Framework into the same transaction.
Our understanding is that this is a distributed transaction, but this feature is not available in Azure.
Is there a way to make this to work in the Azure environment?
e.g.
using (var transaction = new TransactionScope())
{
using (var db = new EntityFrameworkDBEntities())
{
Order order = db.Orders.FirstOrDefault();
order.Name = "1";
db.SaveChanges();
}
using (var legacyAPI = new LegacyAPI())
{
Customer customer = legacyAPI.GetCustomers.FirstOrDefault();
customer.Name = "Charles";
legacyAPI.SaveCustomer(customer);
}
transaction.Complete();
}
AFAIK you need to use the same connection for your transaction since SQL Azure doesn't support distributed transaction. ADO.NET will upgrade to distributed transaction if you utilizes multiple connections in the same transaction even though all of them are connected to the same database.
As Shaun Xu says, you need to use just one connection. If you are able to change your LegacyAPI to take an open connection and a transaction as input, here is how, using EF6 and edmx:
var workspace = new MetadataWorkspace(new[] { "res://*/" }, new[] { Assembly.GetExecutingAssembly() });
using (var connection = new SqlConnection("Normal ADO connection string with MultipleActiveResultSets=True"))
{
using (var entityConnection = new EntityConnection(workspace, connection, false))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
using (var db = new EntityFrameworkDBEntities(entityConnection))
{
db.Database.UseTransaction(transaction);
// Do stuff with db
db.SaveChanges();
}
// Do ADO stuff on LegacyAPI using the connection and transaction objects
transaction.Commit();
}
}
}
To obtain the extra constructor on your dbcontext, you make this partial class, where false indicates that you open and close the connection manually.
partial class EntityFrameworkDBEntities
{
public EntityFrameworkDBEntities(DbConnection connection) : base(connection, false) { }
}
As a bonus you now only need one connection string in your config and it doesn't include all the useless EF junk that normally comes with it (metadata=res://*/blabla).
This also works if, say, you have a database with multiple schemas and an edmx for each. Note that although the EntityConnections are identical, you need one for each dbcontext.
Does someone knows how to call a StoredProc using the same transaction of an objectContext SaveChanges method (EntityFramework 5)?
The goal is to apply the objects changes and call a stored Proc that does some "magic" on the DB, but, if something goes wrong (either with the SaveChanges or with the SP execution) no changes would be committed at all.
Steps:
Create the context
get the connection from the context
Create the transaction (TransactionScope)
Open the connection (will enlist the connection into the ambient transaction created in 3. and will prevent from closing the connection by the context)
Do SaveChanges()
Execute your stored procedure
Commit the transaction
Close the connection
Some code (MyContext is derived from DbContext):
using (var ctx = new MyContext())
{
using (var trx = new TransactionScope())
{
var connection = ((IObjectContextAdapter)ctx).ObjectContext.Connection;
try
{
ctx.Entities.Add(new MyEntity() { Number = 123 });
ctx.SaveChanges();
ctx.Database.ExecuteSqlCommand("INSERT INTO MyEntities VALUES(300)");
trx.Complete();
}
finally
{
connection.Close();
}
}
}
I would like know what is the best possible way to implement transactions with DBContext. In particular,
Does DbContext.SaveChanges implement transaction internall if i change multiple entities?
If i want to call DbContext.SaveChanges multiple times(same contxet/different contxets), how transaction can be achieved?
Yes. SaveChanges uses transaction internally.
Use TransactionScope to wrap multiple calls to SaveChanges
Example:
using(var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
// Do something
context.SaveChanges();
// Do something else
context.SaveChanges();
scope.Complete();
}