Невозможно использовать DbContext.Query внутри транзакции - PullRequest
0 голосов
/ 11 апреля 2019

Я использую EF6 для запроса к базе данных. Пользователь может настроить временную таблицу и запросить данные из временной таблицы. Я использую

DataTable result = context.Query(queryStatement);

чтобы получить результат, и он работал нормально.

Теперь запрос необходим среди серьезных других команд sql, и необходима транзакция. Итак, у меня есть

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);
        }
    }
}

Но это вызывает исключение при выполнении контекста. Запрос

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.

И когда я читаю эту статью: https://docs.microsoft.com/en-us/ef/ef6/saving/transactions Там написано:

Entity Framework не переносит запросы в транзакции.

Это причина этой проблемы?

Как я могу использовать context.Query() внутри транзакции?

Что еще я могу использовать?

Я испробовал все другие методы, ни один из них не работает - потому что тип данных возврата не может быть предсказан заранее.

Я только что понял, что метод Query определен в 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;
            }
        }
    }

Ответы [ 2 ]

0 голосов
/ 11 апреля 2019

Извините, ребята, как упоминалось выше, я думал, что метод Query от EF, но я проверил код и обнаружил, он на самом деле кодируется другим разработчиком, определенным в классе MyDbContext. Поскольку этот класс генерируется EF, и я никогда не думаю, что кто-то добавил метод.

Это

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;
        }
    }
}
0 голосов
/ 11 апреля 2019

Может быть, вы пропустили этот раздел -

вы можете выполнять операции с базой данных либо непосредственно в самом SqlConnection, либо в DbContext.Все такие операции выполняются в рамках одной транзакции.Вы берете на себя ответственность за принятие или откат транзакции и за вызов Dispose () для нее, а также за закрытие и удаление соединения с базой данных

И затем эта кодовая база -

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();
        }
    }
}

Специально для этого -

context.Database.UseTransaction(sqlTxn);
...