6

I have code with mixed EF and normal SQL calls. The whole thing runs on Azure, so we use ReliableSqlConnection. We are using TransactionScope and we don't have the Distributed Transaction Manager (Azure again). Therefore I have to pass the ReliableSqlConnection to every SQL call.

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

Which 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 neither can I convert a ReliableSqlConnection to DbConnection, nor does UniversalModelEntities accept a EntityConnection.

Community
  • 1
  • 1
Remy
  • 12,555
  • 14
  • 64
  • 104

2 Answers2

5

Problem is ReliableSqlConnection implements IDbConnection interface, but EF context constructors all accept DbConnection (not interface). I don't know why they made such decision, maybe they have a valid reasoning behind it, maybe it's just bad design decision. However, you have to live with that. Note that using whatever is returned by ReliableSqlConnection.Open() or ReliableSqlConnection.Current is not an option - it will work yes, but you will just use regular connection then without retrying logic, basically bypassing whole ReliableSqlConnection class purpose. Instead you might try to create a wrapper around ReliableSqlConnection, like this:

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 as EF wants, and forward all logic to the underlying ReliableSqlConnection instance. Note that you might need to override more methods from DbConnection (like Dispose) - here I just show how to override only required (abstract) members.

Alternative option to the wrapper would be copy the source code of ReliableSqlConnection class and modify it to inherit DbConnection.

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

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

Which just calls base class constructor with the same parameters. Second parameter (contextOwnsConnection) defines if context is capable to manage this connection, for example to close it when context is disposed.

If you use EF database first approach - edit EF template which generates code for your context and add 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 investigation I came to a conclusion that approach above might be hard to implement, because connection wrappers are not quite compatible with entity framework. Consider more simple alternative - use DbCommandInterceptor and reuse retry logic with extension methods provided by the same Transient Fault Handler library which provides your ReliableSqlConnection:

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 commands and forward their execution to Transient Fault Handler block. Then in 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();
            }
        }
     }
}
Evk
  • 98,527
  • 8
  • 141
  • 191
  • Thanks for the help. It compiles and all, but I'm getting an Exception System.Data.Entity.Core.ProviderIncompatibleException: A null was returned after calling the 'get_ProviderFactory' method on a store provider instance of type 'SupertextCommon.Util.ReliableSqlConnectionWrapper'. The store provider might not be functioning correctly. at System.Data.Entity.Core.Common.DbProviderServices.GetProviderFactory(DbConnection connection) at System.Data.Entity.Utilities.DbConnectionExtensions.GetProviderInvariantName(DbConnection connection) – Remy May 02 '16 at 16:49
  • That's because EF calls DbProviderFactory property which we did not override (as stated in my answer - you may need to override more stuff than absolute minimum). See my updated answer about how you might override it (added DbProviderFactory property). – Evk May 02 '16 at 16:57
  • Ok, one step further. Now this: System.Data.Entity.Infrastructure.UnintentionalCodeFirstException: The context is being used in Code First mode with code that was generated from an EDMX file for either Database First or Model First development. This will not work correctly. If you are creating your own DbConnection, then make sure that it is an EntityConnection and not some other type of DbConnection, and that you pass it to one of the base DbContext constructors that take a DbConnection. I'm using a "normal" DbConnection, e.g. the ReliableSqlConnection. – Remy May 02 '16 at 17:05
  • Well it's not as easy as I thought then. Will test myself and come back when there is confirmed working solution. – Evk May 02 '16 at 17:17
  • How do you configure retry policy for your connection? Is it application-wide? Do you always have your connection open already when passing to EF? – Evk May 02 '16 at 18:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110852/discussion-between-evk-and-remy). – Evk May 02 '16 at 18:07
  • Works like a charm! Any advice about the MetadataWorkspace? Would you put that into a Singleton? – Remy May 03 '16 at 09:07
  • 1
    Yes sure, getting that workspace is relatively expensive operation and should be done just once. I would put whole procedure of creating your context in separate class (if you use dependency injection container), otherwise singleton should work fine (remember though that with singleton you _might_ have harder time with unit testing). – Evk May 03 '16 at 09:36
1

Have you tried:

using (EntityConnection entityConnection = 
    new EntityConnection(workspace, (DbConnection)conn.Current))
ErikEJ
  • 40,951
  • 5
  • 75
  • 115
  • This will ignore ReliableSqlConnection retry functionality which author wants to have for EF too. – Evk May 03 '16 at 06:50
  • EF has it own Connection resiliency, which can be used – ErikEJ May 03 '16 at 10:19
  • That is true, but it cannot be used inside transaction scope (see limitations here https://msdn.microsoft.com/en-us/data/dn307226), which is just what author tries to do. – Evk May 03 '16 at 10:24