Using the same SqlConnection for EntityFramework and "regular" SQL calls

I have code with mixed EF and normal SQL calls. It all works on Azure, so we use ReliableSqlConnection . We use TransactionScope, and we do not have a Distributed Transaction Manager (again Azure). Therefore, I have to pass ReliableSqlConnection for every SQL call.

Now the problem is how to pass ReliableSqlConnection to an EF call? If you found this message:
How to use Entity Framework ADO.net with existing SqlConnection?

What would lead to this code:

MetadataWorkspace workspace = new MetadataWorkspace( new string[] { "res://*/" }, new Assembly[] { Assembly.GetExecutingAssembly() }); using (var scope = new TransactionScope()) using (var conn = DatabaseUtil.GetConnection()) using (EntityConnection entityConnection = new EntityConnection(workspace, (DbConnection)conn)) using (var db = new UniversalModelEntities(entityConnection)) { //Do EF things //Call other SQL commands return db.SaveChanges(); } 

But I also cannot convert ReliableSqlConnection to DbConnection, and UniversalModelEntities does not accept EntityConnection.

+5
source share
2 answers

Problem ReliableSqlConnection implements the IDbConnection interface, but EF context constructors all accept DbConnection (and not the interface). I don’t know why they made such a decision, maybe they have reasonable arguments, maybe this is just a bad design decision. However, you must live with it. Note that using either ReliableSqlConnection.Open() or ReliableSqlConnection.Current is returned is not an option - it will work yes, but you will just use a regular connection and not a repeat logic, basically bypassing the whole target of the ReliableSqlConnection class. Instead, you can try to create a wrapper around ReliableSqlConnection, for example:

 public class ReliableSqlConnectionWrapper : DbConnection { private readonly ReliableSqlConnection _connection; public ReliableSqlConnectionWrapper(ReliableSqlConnection connection) { _connection = connection; } protected override DbTransaction BeginDbTransaction(System.Data.IsolationLevel isolationLevel) { return (DbTransaction) _connection.BeginTransaction(); } public override void Close() { _connection.Close(); } public override void ChangeDatabase(string databaseName) { _connection.ChangeDatabase(databaseName); } public override void Open() { _connection.Open(); } public override string ConnectionString { get { return _connection.ConnectionString; } set { _connection.ConnectionString = value; } } public override string Database { get { return _connection.Database; } } public override ConnectionState State { get { return _connection.State; } } public override string DataSource { get { return _connection.Current?.DataSource; } } public override string ServerVersion { get { return _connection.Current?.ServerVersion; } } protected override DbCommand CreateDbCommand() { return _connection.CreateCommand(); } protected override DbProviderFactory DbProviderFactory { get { return SqlClientFactory.Instance; } } } 

Here we inherit from DbConnection at the request of EF and forward all the logic to the base instance of ReliableSqlConnection . Please note that you may need to override more methods from DbConnection (e.g. Dispose ) - here I will just show how to override only the required (abstract) elements.

An alternative shell option would copy the source code of the ReliableSqlConnection class and modify it to inherit DbConnection .

Then, in your EF context, you need to add a constructor that accepts DbConnection:

 public UniversalModelEntities(DbConnection connection, bool contextOwnsConnection) : base(connection, contextOwnsConnection) {} 

Which just calls the base class constructor with the same parameters. The second parameter ( contextOwnsConnection ) determines whether the context is able to control this connection, for example, to close it when the context is located.

If you are using the first approach to the EF database, edit the EF template, which generates code for your context and adds this constructor there.

After all this you can do:

 using (var scope = new TransactionScope()) { using (var conn = new ReliableSqlConnection("")) { using (var ctx = new UniversalModelEntities(new ReliableSqlConnectionWrapper(conn), false)) { } } } 

After some research, I came to the conclusion that the approach described above can be difficult to implement because connection wrappers are not entirely compatible with the entity infrastructure. Consider a simpler alternative - use the DbCommandInterceptor and retry the re-logic using the extension methods provided by the same transient error handler library that your ReliableSqlConnection provides:

 public class RetryInterceptor : DbCommandInterceptor { public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { interceptionContext.Result = ((SqlCommand)command).ExecuteNonQueryWithRetry(); } public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { interceptionContext.Result = ((SqlCommand)command).ExecuteReaderWithRetry(); } public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { interceptionContext.Result = ((SqlCommand)command).ExecuteScalarWithRetry(); } } 

So, we intercept the commands and forward their execution to the transient error handler block. Then in the main method:

 static void Main() { // don't forget to add interceptor DbInterception.Add(new RetryInterceptor()); MetadataWorkspace workspace = new MetadataWorkspace( new string[] {"res://*/"}, new[] {Assembly.GetExecutingAssembly()}); // for example var strategy = new FixedInterval("fixed", 10, TimeSpan.FromSeconds(3)); var manager = new RetryManager(new[] {strategy}, "fixed"); RetryManager.SetDefault(manager); using (var scope = new TransactionScope()) { using (var conn = new ReliableSqlConnection("data source=(LocalDb)\\v11.0;initial catalog=TestDB;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework")) { // pass Current - we don't need retry logic from ReliableSqlConnection any more using (var ctx = new TestDBEntities(new EntityConnection(workspace, conn.Current), false)) { // some sample code I used for testing var code = new Code(); code.Name = "some code"; ctx.Codes.Add(code); ctx.SaveChanges(); scope.Complete(); } } } } 
+4
source

You tried:

 using (EntityConnection entityConnection = new EntityConnection(workspace, (DbConnection)conn.Current)) 
+1
source

All Articles