Tony
Tony

Reputation: 652

Cannot Use DbContext.Query inside a transaction

I am using EF6 to query a backend database. User can customize a temporary table and query the data from the temporary table. I am using

DataTable result = context.Query(queryStatement);

to get the result and it has been working fine.

Now the query is needed among a serious of other sqlcommand and a transaction is needed. So I have

public static DataTable GetData()
{
    using (MyDbContext context = new MyDbContext())
    using (DbContextTransaction tran = context.Database.BeginTransaction())
    {
        try
        {
            int rowAffected = context.Database.ExecuteSqlCommand(
                "UPDATE [MyDb].dbo.[TableLocks] SET RefCount = RefCount + 1 WHERE TableName = 'TESTTABLE1'");
            if (rowAffected != 1)
                throw new Exception("Cannot find 'TestTable1'");

            //The following line will raise an exception
            DataTable result = context.Query("SELECT TOP 100 * FROM [MyDb].dbo.[TestTable1]");
            //This line will work if I change it to 
            //context.Database.ExecuteSqlCommand("SELECT TOP 100 * FROM [MyDb].dbo.[TestTable1]");
            //but I don't know how to get the result out of it.
            context.Database.ExecuteSqlCommand(
                "UPDATE [MyDb].dbo.[TableLocks] SET RefCount = RefCount - 1 WHERE TableName = 'TestTable1'");
            tran.Commit();

            return result;
        }
        catch (Exception ex)
        {
            tran.Rollback();
            throw (ex);
        }
    }
}

But this throws an exception while executing context.Query

ExecuteReader requires the command to have a transaction when the connection 
assigned to the command is in a pending local transaction.  The Transaction 
property of the command has not been initialized.

And when I read this article: https://learn.microsoft.com/en-us/ef/ef6/saving/transactions It says:

Entity Framework does not wrap queries in a transaction.

Is it the reason cause this issue?

How can I use context.Query() inside a transaction?

What else I can use?

I tried all other method, none of them work - because the return datatype cannot be predicted before hand.

I just realized that, the Query method is defined in MyDbContext!

    public DataTable Query(string sqlQuery)
    {
        DbProviderFactory dbFactory = DbProviderFactories.GetFactory(Database.Connection);

        using (var cmd = dbFactory.CreateCommand())
        {
            cmd.Connection = Database.Connection;
            cmd.CommandType = CommandType.Text;
            cmd.CommandText = sqlQuery;
            using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
            {
                adapter.SelectCommand = cmd;

                DataTable dt = new DataTable();
                adapter.Fill(dt);

                return dt;
            }
        }
    }

Upvotes: 0

Views: 2115

Answers (2)

Tony
Tony

Reputation: 652

Sorry guys, as mentioned above, I thought the Query method is from EF, but I examined the code and found it is actually coded by another developer, defined in class MyDbContext. Since this class is generated by EF, and I never think somebody have added a method.

It is

public DataTable Query(string sqlQuery)
{
    DbProviderFactory dbFactory = DbProviderFactories.GetFactory(Database.Connection);

    using (var cmd = dbFactory.CreateCommand())
    {
        cmd.Connection = Database.Connection;
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = sqlQuery;

        //And I added this line, then problem solved.
        if (Database.CurrentTransaction != null)
            cmd.Transaction = Database.CurrentTransaction.UnderlyingTransaction;

        using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
        {
            adapter.SelectCommand = cmd;

            DataTable dt = new DataTable();
            adapter.Fill(dt);

            return dt;
        }
    }
}

Upvotes: 1

brainless coder
brainless coder

Reputation: 6430

May be you are missing this section -

you are free to execute database operations either directly on the SqlConnection itself, or on the DbContext. All such operations are executed within one transaction. You take responsibility for committing or rolling back the transaction and for calling Dispose() on it, as well as for closing and disposing the database connection

And then this codebase -

using (var conn = new SqlConnection("..."))
{
    conn.Open();

    using (var sqlTxn = 
    conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
    {
        try
        {
            var sqlCommand = new SqlCommand();
            sqlCommand.Connection = conn;
            sqlCommand.Transaction = sqlTxn;
            sqlCommand.CommandText =
                       @"UPDATE Blogs SET Rating = 5" +
                        " WHERE Name LIKE '%Entity Framework%'";
            sqlCommand.ExecuteNonQuery();

            using (var context =  
                new BloggingContext(conn, contextOwnsConnection: false))
            {
                        context.Database.UseTransaction(sqlTxn);

                        var query =  context.Posts.Where(p => p.Blog.Rating >= 5);
                        foreach (var post in query)
                        {
                            post.Title += "[Cool Blog]";
                        }
                       context.SaveChanges();
                    }

                    sqlTxn.Commit();
        }
        catch (Exception)
        {
             sqlTxn.Rollback();
        }
    }
}

Specially this one -

context.Database.UseTransaction(sqlTxn);

Upvotes: 1

Related Questions