I need to use a Transaction Scope with Entity Framework 4 and a Firebird database. I am using the FireBird Entity Framework provider.
My problem is that once SaveChanges has been called on an object, the data is persisted to the database, instead of when transactionScope.Complete() is called. This results in data never rolling back, even if an exception occurs inside the using (TransactionScope ...) block.
This seems to be a problem with the FireBird DB, I have tested the exact same code with MS SQL 2008 and RollBack works correctly.
What do I need to do to enable Rolling Back with FireBird?
using ( var context = new Model1Container() )
{
bool success = false;
using ( TransactionScope transactionScope = new TransactionScope() )
{
PERSON person = new PERSON();
person.NAME = "test";
context.AddToPERSON(person);
context.SaveChanges(SaveOptions.DetectChangesBeforeSave);
success = true;
//transactionScope.Complete(); If this line is not hit, Transaction should Roll Back, but it does not.
}
if ( success )
{
context.AcceptAllChanges();
}
}
For firebird you need to explicitly say that it has to participate by adding Enlist=True in the connectionstring.
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.
Can u tell me what is the problem?
If you are using two different instances of the DbContext (the db variable as you named it) then nothing will be saved when you call SaveChanges on a context different than the one where your entities are tracked. You need to use the Attach method first.
db.customer_images.Attach(item);
db.SaveChanges();
However I think in your case you can avoid the attach step if you refactor a bit you code and don't use the DbContext from the entity itself.
Before going through my answer, you must check, if you are attaching the item as shown in excepted answer or check this code:.
if (dbStudentDetails != null && dbStudentDetails.Id != 0)
{
// update scenario
item.Id = dbStudentDetails.Id;
_context.Entry(dbStudentDetails).CurrentValues.SetValues(item);
_context.Entry(dbStudentDetails).State = EntityState.Modified;
}
else
{
// create scenario
_context.StudentDetails.Add(item);
}
await _context.SaveChangesAsync();
If above solution doesn't work, then check the below answer.
Saw a very wired issue, and thought to must answer this. as this can
be a major issue if you have lots of constraints and indexes in your
SQL.
db.SaveChanges() wasn't throwing any error, but not working (I have tried Exception or SqlException). This was not working because the Unique constraint was not defined properly while creating the Entity Models.
How you can Identified the issue:
I connected my SQL Server and opened the SQL Profiler.
Just before the db.SaveChanges(), I cleared all my profiler logs and ran the db.SaveChanges(). It logged the statement. I copied the script from the profiler and ran the script in SQL Server.
And bingo, I can see the actual error, which is being thrown at SQL Server side.
(images: have some hints, how you can get the execute statement from Profiler and run on sql server)
What you can do For Entity Framework Core:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Students>().HasIndex(p => new { p.RollNumber, p.PhoneNumber }).IsUnique(true).IsClustered(false).HasDatabaseName("IX_Students_Numbers");
}
What you can do For Entity Framework 6 and below:
using System.Data.Entity.ModelConfiguration;
internal partial class StudentsConfiguration : EntityTypeConfiguration<Students>
{
public StudentsConfiguration()
{
HasIndex(p => new { p.RollNumber, p.PhoneNumber }).IsUnique(true).IsClustered(false).HasName("IX_Students_Numbers");
}
}
Try to query your entity by Id, eg:
entity = this.repo.GetById(item.id);
entity.is_front = false;
if (dbSaveChanges() > 0)
{
....
}
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.
I'm working on a MVC3 application and i'm using the Entity Framework linked to an Oracle database (11G R2).
I'm encountering an issue when i'm trying to use a single object context inside a TransactionScope.
Here is the code :
using (TransactionScope scope = new TransactionScope())
{
using (Entities context = new Entities())
{
// Right insert
T_RIGRIGHT entity1 = new T_RIGRIGHT()
{
RIGCODE = "test1",
RIGINSERTLOGIN = "aco",
RIGINSERTDATE = DateTime.Now,
RIGUPDATELOGIN = "aco",
RIGUPDATEDATE = DateTime.Now
};
context.AddToT_RIGRIGHT(entity1);
context.SaveChanges();
// Right/Profile insert
T_RIPRIGHTPROFILE entity2 = new T_RIPRIGHTPROFILE()
{
PROID = 3,
RIGID = entity1.RIGID,
RIPINSERTLOGIN = "aco",
RIPINSERTDATE = DateTime.Now,
RIPUPDATELOGIN = "aco",
RIPUPDATEDATE = DateTime.Now
};
context.AddToT_RIPRIGHTPROFILE(entity2);
context.SaveChanges(); // SaveChanges fails due to the FK constraint on table
}
scope.Complete();
}
Let me explain the code...
First I create an entity called entity1 as a T_RIGRIGHT element.
The I instanciate a T_RIPRIGHTPROFILE element that uses the id of the T_RIGRIGHT element created before.
The execution fails on the second context.SaveChanges() and the exception concerns the Foreign Key constraint on the table T_RIPRIGHTPROFILE (requires a T_RIGRIGHT).
Hope my explanations are clear enough
Is there any way to make it works ?
P.S. : I apologize for my english as it's not my native language.
You are trying to assign the FK entity1.RIGID of an entity that has not been committed to the DB:
RIGID = entity1.RIGID,
If you look at entity1 closely you will see that RIGID is 0 by default - instead you should set the navigation property representing the FK relationship:
RIG = entity1,
This will enable EF to properly relate these entities, for this entity1 does not have to be committed to the DB yet, so you do not even need the extra SaveChanges() call.
Also in your scenario you do not need a TransactionScope - EF uses a transaction internally already in SaveChanges() - based on the suggested changes you only need one SaveChanges() call and hence no outer transaction scope is needed.
I'm currently working on a project which is using EF Code First with POCOs. I have 5 POCOs that so far depends on the POCO "User".
The POCO "User" should refer to my already existing MemberShip table "aspnet_Users" (which I map it to in the OnModelCreating method of the DbContext).
The problem is that I want to take advantage of the "Recreate Database If Model changes" feature as Scott Gu shows at: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - What the feature basically does is to recreate the database as soon as it sees any changes in my POCOs. What I want it to do is to Recreate the database but to somehow NOT delete the whole Database so that aspnet_Users is still alive. However it seems impossible as it either makes a whole new Database or replaces the current one with..
So my question is: Am I doomed to define my database tables by hand, or can I somehow merge my POCOs into my current database and still take use of the feature without wipeing it all?
As of EF Code First in CTP5, this is not possible. Code First will drop and create your database or it does not touch it at all. I think in your case, you should manually create your full database and then try to come up with an object model that matches the DB.
That said, EF team is actively working on the feature that you are looking for: altering the database instead of recreating it:
Code First Database Evolution (aka Migrations)
I was just able to do this in EF 4.1 with the following considerations:
CodeFirst
DropCreateDatabaseAlways
keeping the same connection string and database name
The database is still deleted and recreated - it has to be to for the schema to reflect your model changes -- but your data remains intact.
Here's how: you read your database into your in-memory POCO objects, and then after the POCO objects have successfully made it into memory, you then let EF drop and recreate the database. Here is an example
public class NorthwindDbContextInitializer : DropCreateDatabaseAlways<NorthindDbContext> {
/// <summary>
/// Connection from which to ead the data from, to insert into the new database.
/// Not the same connection instance as the DbContext, but may have the same connection string.
/// </summary>
DbConnection connection;
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map;
public NorthwindDbContextInitializer(DbConnection connection, Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> map = null) {
this.connection = connection;
this.map = map ?? ReadDataIntoMemory();
}
//read data into memory BEFORE database is dropped
Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> ReadDataIntoMemory() {
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map = new Dictionary<Tuple<PropertyInfo,Type>,System.Collections.IEnumerable>();
switch (connection.State) {
case System.Data.ConnectionState.Closed:
connection.Open();
break;
}
using (this.connection) {
var metaquery = from p in typeof(NorthindDbContext).GetProperties().Where(p => p.PropertyType.IsGenericType)
let elementType = p.PropertyType.GetGenericArguments()[0]
let dbsetType = typeof(DbSet<>).MakeGenericType(elementType)
where dbsetType.IsAssignableFrom(p.PropertyType)
select new Tuple<PropertyInfo, Type>(p, elementType);
foreach (var tuple in metaquery) {
map.Add(tuple, ExecuteReader(tuple));
}
this.connection.Close();
Database.Delete(this.connection);//call explicitly or else if you let the framework do this implicitly, it will complain the connection is in use.
}
return map;
}
protected override void Seed(NorthindDbContext context) {
foreach (var keyvalue in this.map) {
foreach (var obj in (System.Collections.IEnumerable)keyvalue.Value) {
PropertyInfo p = keyvalue.Key.Item1;
dynamic dbset = p.GetValue(context, null);
dbset.Add(((dynamic)obj));
}
}
context.SaveChanges();
base.Seed(context);
}
System.Collections.IEnumerable ExecuteReader(Tuple<PropertyInfo, Type> tuple) {
DbCommand cmd = this.connection.CreateCommand();
cmd.CommandText = string.Format("select * from [dbo].[{0}]", tuple.Item2.Name);
DbDataReader reader = cmd.ExecuteReader();
using (reader) {
ConstructorInfo ctor = typeof(Test.ObjectReader<>).MakeGenericType(tuple.Item2)
.GetConstructors()[0];
ParameterExpression p = Expression.Parameter(typeof(DbDataReader));
LambdaExpression newlambda = Expression.Lambda(Expression.New(ctor, p), p);
System.Collections.IEnumerable objreader = (System.Collections.IEnumerable)newlambda.Compile().DynamicInvoke(reader);
MethodCallExpression toArray = Expression.Call(typeof(Enumerable),
"ToArray",
new Type[] { tuple.Item2 },
Expression.Constant(objreader));
LambdaExpression lambda = Expression.Lambda(toArray, Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(tuple.Item2)));
var array = (System.Collections.IEnumerable)lambda.Compile().DynamicInvoke(new object[] { objreader });
return array;
}
}
}
This example relies on a ObjectReader class which you can find here if you need it.
I wouldn't bother with the blog articles, read the documentation.
Finally, I would still suggest you always back up your database before running the initialization. (e.g. if the Seed method throws an exception, all your data is in memory, so you risk your data being lost once the program terminates.) A model change isn't exactly an afterthought action anyway, so be sure to back your data up.
One thing you might consider is to use a 'disconnected' foreign key. You can leave the ASPNETDB alone and just reference the user in your DB using the User key (guid). You can access the logged in user as follows:
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
And then use the User's key as a FK in your DB:
Guid UserId = (Guid) currentUser.ProviderUserKey ;
This approach decouples your DB with the ASPNETDB and associated provider architecturally. However, operationally, the data will of course be loosely connected since the IDs will be in each DB. Note also there will be no referential constraints, whcih may or may not be an issue for you.