Efcore: Using .Where() with variable causes transaction exception, regression RC1 -> RC2

Created on 30 May 2016  路  6Comments  路  Source: dotnet/efcore

Consider the following code

[TestClass]
public sealed class Rc2Tests : MyDbTests
{
    public int ID { get; set; } = 1;

    [TestMethod]
    public async Task ForEachAsyncTest()
    {
        MyDbContext db = this.GetRequiredService<MyDbContext>();
        CancellationToken ct = CancellationToken.None;

        db.Projects.Add(new DbProject("Test", "Test"));
        await db.SaveChangesAsync(ct);

        int id = 1;
        using (IDbContextTransaction t = await db.Database.BeginTransactionAsync(ct))
        {
            await db.Projects
                .Where(p => p.Id == 1)
                .ForEachAsync(p =>
                {
                    p.DisplayName = "NewName1";
                }, ct);
            await db.SaveChangesAsync(ct);
            t.Commit();
        }
        int[] ids = new[] { id };
        using (IDbContextTransaction t = await db.Database.BeginTransactionAsync(ct))
        {
            await db.Projects
                .Where(p => ids.Contains(p.Id))
                .ForEachAsync(p =>
                {
                    p.DisplayName = "NewName2";
                }, ct);
            await db.SaveChangesAsync(ct);
            t.Commit();
        }
        using (IDbContextTransaction t = await db.Database.BeginTransactionAsync(ct))
        {
            await db.Projects
                .Where(p => p.Id == id) // Throws System.Data.SqlClient.SqlException: The transaction operation cannot be performed because there are pending requests working on this transaction.
                .ForEachAsync(p =>
                {
                    p.DisplayName = "NewName3";
                }, ct);
            await db.SaveChangesAsync(ct);
            t.Commit();
        }
        using (IDbContextTransaction t = await db.Database.BeginTransactionAsync(ct))
        {
            await db.Projects
                .Where(p => p.Id == this.ID) // Also throws System.Data.SqlClient.SqlException: The transaction operation cannot be performed because there are pending requests working on this transaction.
                .ForEachAsync(p =>
                {
                    p.DisplayName = "NewName3";
                }, ct);
            await db.SaveChangesAsync(ct);
            t.Commit();
        }
    }
}

The line with // Throws System.Data.SqlClient.SqlException causes the following at t.Commit();

System.Data.SqlClient.SqlException: The transaction operation cannot be performed because there are pending requests working on this transaction.
    at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlInternalTransaction.Commit()
   at System.Data.SqlClient.SqlTransaction.Commit()
   at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction.Commit()

The DB contains "NewName2"

This was working in RC1.
Please help, I am desperate. Any idea for a workaround would be great.

type-bug

All 6 comments

I found a possible fix

int id = 1;
using (IDbContextTransaction t = await db.Database.BeginTransactionAsync(ct))
{
    await db.Projects
        .Where(p => p.Id == EfIssue.Fix(id))
        .ForEachAsync(p => { p.DisplayName = "Fixed"; }, ct);
    await db.SaveChangesAsync(ct);
    t.Commit();
}

With

public static class EfIssue
{
    /// <summary>
    /// Fixes https://github.com/aspnet/EntityFramework/issues/5579
    /// by returning the value you give to it.
    /// </summary>
    public static T Fix<T>(T value)
    {
        return value;
    }
}

Possible dupe of #5440

@Mertsch Can you explain the fix? Also, are you able to re-test this against a nightly build? You can use this feed: https://www.myget.org/F/aspnetvnext/api/v3/index.json

@anpete The fix EfIssue.Fix(id) just changes the signature of the anonymous method created.
Instead of a property / variable accessor a method is invoked, this apparently changes (and corrects) the way the linq query is analyzed.

I will try to check against the nightly, but it will take a day or two.

@Mertsch We think this is likely fixed post RC2. Please feel free to re-open if this is still an issue.

@anpete Confirmed fixed in 1.0 RTM

Was this page helpful?
0 / 5 - 0 ratings