Using EF transaction across libraries - c#

I have this class A that begins an EF transaction where UserDb is my DbContext
using (DbContextTransaction dbTransaction = UserDb.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
Then I have several inserts and there is a need to call another library [which essentially lives on the same server in the bin folder] to do another insert.
new ExtLibrary().CreatePoweruser(3, UserDb);
As you can see I am passing the same connection. And this statement is within the top using which I thought would mean that everythign is in the same transaction.
Extlibrary code:
Data.Entities.User UserEntity = new Data.Entities.User {
UserTypeId =34,
CreatedDate = DateTime.Now,
CreatedBy = "mk92Test",
};
UserDb.Users.Add(UserEntity);
UserDb.SaveChanges();
Everything works unless the ExtLibrary insert fails. Control comes back to the parent class which has rollback code on exception and I get an The underlying provider failed on rollback. But the first set of inserts certainly do rollback even after this exception.
Please advise.

Related

Unable to begin a distributed transaction using Entiy Framework

I'm having the following error executing this piece of code:
private bool _updateList(SysInfo _sysInfo, List<pList> _pList)
{
try
{
foreach (var p in _pList)
{
_context.spUpdatePListApprovalFlow(p.countryID, _sysInfo.User.JobRoleID, p.src, p.id, p.status, _sysInfo.User.Username);
}
return true;
}
catch (Exception ex) //debug only
{
throw; //throws error to the main try catch
}
}
ERROR
The operation could not be performed because OLE DB provider "MSDASQL"
for linked server "AS400_LINKEDSRV" was unable to begin a distributed
transaction.
However, everything works fine when I run the Stored Procedure in SQL Management Studio:
exec [dbo].[spUpdatePListApprovalFlow]
#CountryID = 123456,
#UserTypeID = 23456,
#Src = 1,
#Id = '123456789',
#Status = 30,
#Username = 'username'
I'm tired of digging for an answer nothing works... Few things I've tried:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
insert multiple transactions in the stored procedure
This sp has 2 sub stored procedures on it. One that writes into a table of the application's database, and another that updates a table in as400.
In EF6 stored procedures are called in an explicit transaction, by default. You can turn this off for a particular DbContext instance by changing its configuration after creating it, or for all instances of a DbContext type by changing it in the constructor. EG
using (var db = new Db())
{
db.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
//. . .
}
See: https://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.dbcontextconfiguration.ensuretransactionsforfunctionsandcommands
Ok, after half a day trying to solve this I've finally solved it.
Resolution
Downgraded from Entity Framework 6 to Entity Framework 5 and the
distribuited transactions error has gone.
Just pay attention, if you're going to do this, you have to change some usings in your code. ( in auto generated code in the Data Model as well)
EF 6 uses
using System.Data.Entity.Core.Objects
EF 5 uses
using System.Data.Objects;
If you don't need distributed transactions you can try to disable them in the settings of the linked server:
EXEC master.dbo.sp_serveroption
#server=N'AS400_LINKEDSRV',
#optname=N'remote proc transaction promotion',
#optvalue=N'false'
Refer to this Microsoft page on Linked Servers.
Your System Administrator and/or DBA will probably need to make changes to address the missing linked server definition to your AS/400 server.
Another possible issue is that the AS/400 server (from IBM) lacks software support for the OLE DB data sources. This too would be something that the System Administration staff may need to address.

Stored Procedure without transaction in Entity Framework

I'm calling a stored procedure in Entity Framework 6 that can create Databases and tables if necessary. It is throwing the error;
Message "CREATE DATABASE statement not allowed within multi-statement transaction.\r\nALTER DATABASE statement not allowed within multi-statement transaction.\r\nDatabase 'CoreSnapshotJS3' does not exist. Make sure that the name is entered correctly." string
I do not want it in a transaction, and have used this to supress the transaction
using (var transation = new TransactionScope(TransactionScopeOption.Suppress))
{
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("spCreateSnapshotFromQueue", snapshotQueueIDParameter);
}
It still throws an error.
How do I stop automatic transactions?
I found a way:
var snapshotQueueIDParameter = new SqlParameter("SnapshotQueueID", entityId);
return _db.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction,
"EXEC spCreateSnapshotFromQueue #SnapshotQueueID", snapshotQueueIDParameter);

Error passing existing connections to DbContext constructor when using Database-First

I'm trying to create a class to perform work on the database and have the need (or preference) to use a combination of DbContext and good old fashioned ADO. Why, well EF is great for simplifying a great deal of code but ADO still has many uses for more complex methods that EF cannot yet handle.
This link on MSDN states that I can pass an existing SqlConnection to my context as follows:
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var context = new SampleContext(conn, contextOwnsConnection: false))
{
// Do Something
}
}
Now I'm using Database-First so this constructor doesn't appear as standard. I therefore created a new Partial Class file and created the appropriate constructor as follows:
public partial class MyEntities : DbContext
{
public MyEntities(System.Data.Common.DbConnection conn, bool contextOwnsConnection = false)
: base(existingConnection: conn, contextOwnsConnection: contextOwnsConnection)
{
}
}
However, when I run the code the moment it hits a call to the new DbContext constructor, I get the following UnintentionalCodeFirstException() error thrown by OnModelCreating in my EDMX file:
"Code generated using the T4 templates for Database First and Model First development may not work correctly if used in Code First mode. To continue using Database First or Model First ensure that the Entity Framework connection string is specified in the config file of executing application. To use these classes, that were generated from Database First or Model First, with Code First add any additional configuration using attributes or the DbModelBuilder API and then remove the code that throws this exception."
Am I missing something obvious here, or can it just not be done with Database-First?
Clearly, I could just use two connections, one for my SqlConnection object, and another for my DbContext object but if I can, naturally I'd prefer to use a single connection if possible.
Any and all help greatly appreciated. For full disclosure, I'm using SQL-Server 2012, .NET 4.5.1, C# and EF6.0.2.
Connection strings used by the designer are not regular connection strings. Rather they are EntityConnection strings. The difference is that entity connection strings contain additional information about where to find metadata describing the model which is in form of the edmx at design time - read more here. Code First uses just regular connection strings since it builds the model on the fly based on the code. So, the UnintentionalCodeFirstException is preventing the user from using CodeFirst functionality with edmx models because the model is specified in the edmx and not in the code and if it was allowed you would effectively end up using two different models (one from edmx and one built from the code) which very likely won't be in sync which would result in weird behavior or even could lead to data corruption and crashes/exceptions.
Since the EntityConnection is derived from DbConnection and just wraps regular connection you can use it in places where you would use the provider connection. Alternatively you can access the wrapped provider connection using the StoreConnection provider on the EntityConnection.
I know this is an old thread, but later versions of Entity Framework actually can handle a shared connection, so I offer this as an alternative answer.
You can initialize an instance of the entity container with a shared connection. Use the EntityConnection(MetadataWorkspace workspace, DbConnection connection, bool entityConnectionOwnsStoreConnection) overload and specify false for the entityConnectionOwnsStoreConnection parameter. Then pass it into your context constructor as the existing connection. The EntityConnection will then prevent the connection from being automatically closed and disposed with the context.
Example:
using (var conn = new SqlConnection("..."))
{
conn.Open();
// Execute some ADO queries.
var md = new MetadataWorkspace(new[]{"res://*/SampleModel.csdl","res://*/SampleModel.ssdl","res://*/SampleModel.msl"}, new[]{System.Reflection.Assembly.GetExecutingAssembly()});
// Create the EntityConnection so the existing connection is not disposed.
var ec = new EntityConnection(md, conn, false);
using (var context = new SampleContext(conn, contextOwnsConnection: false))
{
// Do something using the entity context.
}
// Entity context is disposed but connection remains open.
// Do more ADO stuff.
}
Yes, it was a pain to figure out this stuff by examining System.Data and Entity Framework source code.
This pattern may be used within a TransactionScope to prevent escalation to a distributed transaction by virtue of using the same database connection.

SqLite in memory database : CREATE TABLE does not work?

When using this code on a SqLite file database, it works fine.
using (var ctx = new Test2010Entities())
{
string s = "CREATE TABLE 'Company' ([Id] integer PRIMARY KEY AUTOINCREMENT NOT NULL, varchar(50) NOT NULL);";
ctx.ExecuteStoreCommand(s);
ctx.Companies.AddObject(new Company { Code = "_1" });
ctx.Companies.AddObject(new Company { Code = "_2" });
ctx.SaveChanges();
foreach (var c in ctx.Companies.ToList())
{
Console.WriteLine(c.Code);
}
}
But when runnning this code on a SqLite 'In Memory' database (Data Source=:memory:;Version=3;New=True;) , I get this exception:
Unhandled Exception: System.Data.UpdateException: An error occurred
while updating the entries. See the inner exception for details. --->
System.Data.SQLite.SQLiteException: SQL logic error or missing
database no such table: Company
Note this is tested with VS 2010, EF 4.4.0.0 and sqlite-netFx40-setup-bundle-x86-2010-1.0.84.0
::: UPDATE :::
As Simon Svensson suggested, opening the connection before any other commands does do the trick:
ctx.Connection.Open();
This happens when your ORM closes your connection, and reopens it. That will reset the sqlite in-memory database to its default state; i.e. empty.
The same thing happens with NHibernate unless you set connection.release_mode = close (the default is after_transaction.
I'm not familiar with Entity Framework, but I expect a similar setting or using the DataContext(IDbConnection) constructor which is documented as "If you provide an open connection, the DataContext will not close it."
The same documentation also states "In a System.Transactions transaction, a DataContext will not open or close a connection to avoid promotion." which may be a cleaner solution.
Using some Reflector magic shows that it's SQLiteConnection.Open that calls (via SQLite3.Open) sqlite3_open_interop (if you're using the NuGet sqlite package). This shows that you get a new empty in-memory database everytime you call SQLiteConnection.Open.

EntityFramework CodeFirst and EDMX together in TransactionScope

I need to combine in one application
code generated from DB to EDMX file before compilation and
code generated and compiled during runtime by application itself, where generated code uses CodeFirst to access DB.
Remark: Codes in 1. and 2. have different DbContexts, access same database, but different tables.
It looks, that when I am using instances of type 1. and 2. in different transaction scopes, everything works fine. But when I try to use them together in one transaction scope, I get error (in case when EDMX is called first)
System.Reflection.TargetInvocationException: Exception has been thrown by the ta
rget of an invocation. ---> System.Data.ProviderIncompatibleException: An error
occurred while getting provider information from the database. This can be cause
d by Entity Framework using an incorrect connection string. Check the inner exce
ptions for details and ensure that the connection string is correct. ---> System
.Data.ProviderIncompatibleException: **The provider did not return a ProviderManif
estToken string.** ---> System.Transactions.TransactionException: **The operation is
not valid for the state of the transaction.**
and error (in case when CodeFirst is used first)
System.Data.MetadataException: Schema specified is not valid. Errors:
(0,0) : error 0175: **The specified store provider cannot be found in the configur
ation, or is not valid.**
at System.Data.Metadata.Edm.StoreItemCollection.Loader.ThrowOnNonWarningErrors()
To complicate my situation even more I have to add this: described behaviour I have only in case when DB is on remote server. If I am working with local DB, everything looks OK. My suspicion is that Distributed Transaction Coordinator can play its role...
Main question: is it possible to combine EDMX and CodeFirst in one TransactionScope. If yes then how?
Any help would be appreciated. Milos
CodeFirst creates an EDMX based on your classes and it's not possible to load an exisiting edmx file. However you can generate classes from your database (e.g. using EF Power Tools) and configure your model so that the EDMX generated by your CodeFirst app is the same as the one you would like to load. You can use TransactionScope with Entity Framework. The error messages you are getting are not related to transaction scope but to a missing or incorrectly used provider.
I used code first to save to two different databases came up with errors connection string was wrong so i did this and it worked....
try
{
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))
{
MegaBotExtractorDBContext2 db = new MegaBotExtractorDBContext2();
MegaBotExtractorDBContext db1 = new MegaBotExtractorDBContext();
FullUri newUri = new FullUri();
HostUri NewHostUri = new HostUri { HostUriName = "google10.com" };
db1.HostUris.Add(NewHostUri);
db1.SaveChanges();
using (TransactionScope ts2 = new TransactionScope(TransactionScopeOption.RequiresNew))
{
db.FullUris.Add(newUri);
db.SaveChanges();
ts2.Complete();
ts.Complete();
}
}
}
catch { }

Categories

Resources