web-dev-qa-db-ja.com

Entity FrameworkからのSqlException - セッション内で他のスレッドが実行されているため、新しいトランザクションは許可されません

私は現在このエラーを受けています:

System.Data.SqlClient.SqlException:セッション内で他のスレッドが実行されているため、新しいトランザクションは許可されません。

このコードを実行しながら:

public class ProductManager : IProductManager
{
    #region Declare Models
    private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
    private RivWorks.Model.NegotiationAutos.RivFeedsEntities _dbFeed = RivWorks.Model.Stores.FeedEntities(AppSettings.FeedAutosEntities_connString);
    #endregion

    public IProduct GetProductById(Guid productId)
    {
        // Do a quick sync of the feeds...
        SyncFeeds();
        ...
        // get a product...
        ...
        return product;
    }

    private void SyncFeeds()
    {
        bool found = false;
        string feedSource = "AUTO";
        switch (feedSource) // companyFeedDetail.FeedSourceTable.ToUpper())
        {
            case "AUTO":
                var clientList = from a in _dbFeed.Client.Include("Auto") select a;
                foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
                {
                    var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
                    foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                    {
                        if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
                        {
                            var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
                            foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
                            {
                                foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
                                {
                                    if (targetProduct.alternateProductID == sourceProduct.AutoID)
                                    {
                                        found = true;
                                        break;
                                    }
                                }
                                if (!found)
                                {
                                    var newProduct = new RivWorks.Model.Negotiation.Product();
                                    newProduct.alternateProductID = sourceProduct.AutoID;
                                    newProduct.isFromFeed = true;
                                    newProduct.isDeleted = false;
                                    newProduct.SKU = sourceProduct.StockNumber;
                                    company.Product.Add(newProduct);
                                }
                            }
                            _dbRiv.SaveChanges();  // ### THIS BREAKS ### //
                        }
                    }
                }
                break;
        }
    }
}

モデル#1 - このモデルは開発サーバーのデータベースにあります。 モデル#1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png

モデル#2 - このモデルは私達のProdサーバー上のデータベースにあり、自動フィードによって毎日更新されます。 代替テキストhttp://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

注 - モデル#1の赤い丸で囲まれた項目は、モデル#2に「マップ」するために使用するフィールドです。モデル#2の赤い丸は無視してください。これは、もう1つの質問からの回答です。

注:isDeletedチェックを入れる必要があるので、クライアントのインベントリから削除された場合はDB1から削除してください。

私がやりたいことは、この特定のコードを使用して、DB1の会社をDB2のクライアントに接続し、DB2から製品リストを取得し、まだ存在していない場合はDB1に挿入することです。初回は、在庫を完全に引き出す必要があります。毎晩新しいインベントリがフィードに入ってこない限り、何も起こらないようにしてそこで実行されるたびに。

それで大きな問題 - 私が得ているトランザクションエラーをどうやって解決するのですか?ループを通して毎回自分のコンテキストを削除して再作成する必要がありますか(私には意味がありません)

547
Keith Barrows

毛を抜いた後、私はforeachループが原因であることを発見しました。何をする必要があるかというと、EFを呼び出して、それをそのターゲット型のIList<T>に返してから、IList<T>をループすることです。

例:

IList<Client> clientList = from a in _dbFeed.Client.Include("Auto") select a;
foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
{
   var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
    // ...
}
627
Keith Barrows

すでに確認したように、アクティブリーダーを介してデータベースからまだ描画しているforeach内から保存することはできません。

ToList()またはToArray()の呼び出しは小さなデータセットには問題ありませんが、数千行になると大量のメモリを消費することになります。

行をまとめてロードするほうが良いでしょう。

public static class EntityFrameworkUtil
{
    public static IEnumerable<T> QueryInChunksOf<T>(this IQueryable<T> queryable, int chunkSize)
    {
        return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk);
    }

    public static IEnumerable<T[]> QueryChunksOfSize<T>(this IQueryable<T> queryable, int chunkSize)
    {
        int chunkNumber = 0;
        while (true)
        {
            var query = (chunkNumber == 0)
                ? queryable 
                : queryable.Skip(chunkNumber * chunkSize);
            var chunk = query.Take(chunkSize).ToArray();
            if (chunk.Length == 0)
                yield break;
            yield return chunk;
            chunkNumber++;
        }
    }
}

上記の拡張メソッドを考えると、あなたはこのようにあなたのクエリを書くことができます:

foreach (var client in clientList.OrderBy(c => c.Id).QueryInChunksOf(100))
{
    // do stuff
    context.SaveChanges();
}

このメソッドを呼び出すクエリ可能オブジェクトは順序付けされている必要があります。Entity Frameworkは順序付きクエリでIQueryable<T>.Skip(int)のみをサポートしているため、範囲が異なる場合は、順序が安定している必要があります。順序が重要でない場合は、クラスタ化インデックスがある可能性が高いので、主キーで並べ替えてください。

このバージョンは100のバッチでデータベースに問い合わせます。SaveChanges()が各実体のために呼ばれることに注意してください。

スループットを劇的に向上させたい場合は、SaveChanges()をそれほど頻繁に呼び出さないでください。代わりにこのようなコードを使用してください。

foreach (var chunk in clientList.OrderBy(c => c.Id).QueryChunksOfSize(100))
{
    foreach (var client in chunk)
    {
        // do stuff
    }
    context.SaveChanges();
}

これにより、データベース更新呼び出しが100分の1になります。もちろん、これらの呼び出しはそれぞれ完了までに時間がかかりますが、最終的にはまだ先に進みます。あなたの走行距離は異なるかもしれませんが、これは私にとって世界の方が早かったです。

そしてそれはあなたが見ていた例外を回避します。

EDITSQLプロファイラを実行した後にこの質問に戻り、パフォーマンスを改善するためにいくつかの点を更新しました。興味のある人のために、DBによって作成されたものを示すサンプルSQLをいくつか示します。

最初のループは何もスキップする必要はないので、もっと簡単です。

SELECT TOP (100)                     -- the chunk size 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM [dbo].[Clients] AS [Extent1]
ORDER BY [Extent1].[Id] ASC

後続の呼び出しでは、結果の以前のチャンクをスキップする必要があるため、row_numberの使用法を紹介します。

SELECT TOP (100)                     -- the chunk size
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM (
    SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], row_number()
    OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
    FROM [dbo].[Clients] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 100   -- the number of rows to skip
ORDER BY [Extent1].[Id] ASC
251
Drew Noakes

への正式な返答を掲載しました。Connectは でオープンしました。推奨される回避策は次のとおりです。

このエラーは、SaveChanges()呼び出し中にEntity Frameworkが暗黙的なトランザクションを作成するために発生します。エラーを回避するための最善の方法は、異なるパターンを使用する(つまり、読み取り中は保存しない)か、トランザクションを明示的に宣言することです。これは3つの可能な解決策です:

// 1: Save after iteration (recommended approach in most cases)
using (var context = new MyContext())
{
    foreach (var person in context.People)
    {
        // Change to person
    }
    context.SaveChanges();
}

// 2: Declare an explicit transaction
using (var transaction = new TransactionScope())
{
    using (var context = new MyContext())
    {
        foreach (var person in context.People)
        {
            // Change to person
            context.SaveChanges();
        }
    }
    transaction.Complete();
}

// 3: Read rows ahead (Dangerous!)
using (var context = new MyContext())
{
    var people = context.People.ToList(); // Note that this forces the database
                                          // to evaluate the query immediately
                                          // and could be very bad for large tables.

    foreach (var person in people)
    {
        // Change to person
        context.SaveChanges();
    }
} 
119

foreach(loop)の最後にcontext.SaveChanges()を追加するだけです。

14
Majid

実際、E#Frameworkを使用してC#のforeachループ内に変更を保存することはできません。

context.SaveChanges()メソッドは、通常のデータベースシステム(RDMS)に対するコミットのように機能します。

データベースコミットコマンドのように、(Entity Frameworkがキャッシュする)すべての変更を加え、ループの後に(その外側で)SaveChanges()を呼び出すだけで、それらすべてを一度に保存できます。

一度にすべての変更を保存できる場合、これは機能します。

10

参考までに:本とその行の有効性があるために調整されたいくつかの行から:

SaveChanges()メソッドを呼び出すと、反復が完了する前に例外が発生した場合にデータベースに保存されているすべての変更を自動的にロールバックするトランザクションが開始されます。そうでなければ、トランザクションはコミットします。特に大量のエンティティを更新または削除する場合は、反復の完了後ではなく、エンティティの更新または削除のたびにメソッドを適用することをお勧めします。

すべてのデータが処理される前にSaveChanges()を呼び出そうとすると、「セッション内で他のスレッドが実行されているため、新しいトランザクションは許可されません」という例外が発生します。接続文字列で複数アクティブレコードセット(MARS)が有効になっていても、SQL ServerはSqlDataReaderが開いている接続で新しいトランザクションを開始できないため、例外が発生します(EFの既定の接続文字列でMARSが有効になります)

何が起きているのかを理解した方がよい場合もあります;-)

7

常に選択したものをリストとして使用する

例えば:

var tempGroupOfFiles = Entities.Submited_Files.Where(r => r.FileStatusID == 10 && r.EventID == EventId).ToList();

変更を保存しながらコレクションをループします

 foreach (var item in tempGroupOfFiles)
             {
                 var itemToUpdate = item;
                 if (itemToUpdate != null)
                 {
                     itemToUpdate.FileStatusID = 8;
                     itemToUpdate.LastModifiedDate = DateTime.Now;
                 }
                 Entities.SaveChanges();

             }
4
mzonerz

私はこれと同じ問題を抱えていましたが、状況は異なります。リストボックスに項目のリストがありました。ユーザーはアイテムをクリックして削除を選択できますが、アイテムの削除には多くのロジックが含まれるため、ストアドプロシージャを使用してアイテムを削除しています。ストアドプロシージャを呼び出すと、削除は正常に機能しますが、今後SaveChangesを呼び出すとエラーが発生します。私の解決策はEFの外でストアドプロシージャを呼び出すことでした、そしてこれはうまくいきました。何らかの理由で、EFのやり方でストアドプロシージャを呼び出すと、何かが開いたままになります。

4
MikeKulls

これが、それぞれのforループでSaveChanges()を呼び出すことを可能にするもう2つのオプションです。

最初の選択肢は、1つのDBContextを使用して繰り返し処理するリストオブジェクトを生成し、次にSaveChanges()を呼び出す2番目のDBContextを作成することです。これが一例です。

//Get your IQueryable list of objects from your main DBContext(db)    
IQueryable<Object> objects = db.Object.Where(whatever where clause you desire);

//Create a new DBContext outside of the foreach loop    
using (DBContext dbMod = new DBContext())
{   
    //Loop through the IQueryable       
    foreach (Object object in objects)
    {
        //Get the same object you are operating on in the foreach loop from the new DBContext(dbMod) using the objects id           
        Object objectMod = dbMod.Object.Find(object.id);

        //Make whatever changes you need on objectMod
        objectMod.RightNow = DateTime.Now;

        //Invoke SaveChanges() on the dbMod context         
        dbMod.SaveChanges()
    }
}

2番目のオプションは、DBContextからデータベースオブジェクトのリストを取得することですが、IDだけを選択することです。そして、idのリスト(おそらくint)を繰り返し処理して各intに対応するオブジェクトを取得し、その方法でSaveChanges()を呼び出します。このメソッドの背後にある考え方は、整数の大きなリストを取得することです。dbオブジェクトの大きなリストを取得し、オブジェクト全体に対して.ToList()を呼び出すよりもはるかに効率的です。これはこのメソッドの例です:

//Get the list of objects you want from your DBContext, and select just the Id's and create a list
List<int> Ids = db.Object.Where(enter where clause here)Select(m => m.Id).ToList();

var objects = Ids.Select(id => db.Objects.Find(id));

foreach (var object in objects)
{
    object.RightNow = DateTime.Now;
    db.SaveChanges()
}
3
jjspierx

私は巨大なResultSetを読み、テーブル内のいくつかのレコードを更新する必要がありました。私は Drew Noakes答え で示唆されているようにチャンクを使おうとしました。

残念ながら、50000レコードを超えると、OutofMemoryExceptionが発生します。答え Entity Frameworkの大規模データセット、メモリ不足例外 は、

EFは、変更の検出に使用するデータの2番目のコピーを作成します(その結果、データベースへの変更を保持できます)。 EFはこの2番目のセットをコンテキストの存続期間の間保持し、このセットはユーザーをメモリー不足にします。

推奨事項は、バッチごとにコンテキストを更新することです。

そのため、主キーの最小値と最大値を取得しました。テーブルには自動増分整数として主キーがあります。次に、各チャンクのコンテキストを開いてデータベースからレコードのチャンクを取得しました。処理後、チャンクコンテキストはメモリを閉じて解放します。それはメモリ使用量が増加していないことを保証します。

以下は私のコードの抜粋です。

  public void ProcessContextByChunks ()
  {
        var tableName = "MyTable";
         var startTime = DateTime.Now;
        int i = 0;
         var minMaxIds = GetMinMaxIds();
        for (int fromKeyID= minMaxIds.From; fromKeyID <= minMaxIds.To; fromKeyID = fromKeyID+_chunkSize)
        {
            try
            {
                using (var context = InitContext())
                {   
                    var chunk = GetMyTableQuery(context).Where(r => (r.KeyID >= fromKeyID) && (r.KeyID < fromKeyID+ _chunkSize));
                    try
                    {
                        foreach (var row in chunk)
                        {
                            foundCount = UpdateRowIfNeeded(++i, row);
                        }
                        context.SaveChanges();
                    }
                    catch (Exception exc)
                    {
                        LogChunkException(i, exc);
                    }
                }
            }
            catch (Exception exc)
            {
                LogChunkException(i, exc);
            }
        }
        LogSummaryLine(tableName, i, foundCount, startTime);
    }

    private FromToRange<int> GetminMaxIds()
    {
        var minMaxIds = new FromToRange<int>();
        using (var context = InitContext())
        {
            var allRows = GetMyTableQuery(context);
            minMaxIds.From = allRows.Min(n => (int?)n.KeyID ?? 0);  
            minMaxIds.To = allRows.Max(n => (int?)n.KeyID ?? 0);
        }
        return minMaxIds;
    }

    private IQueryable<MyTable> GetMyTableQuery(MyEFContext context)
    {
        return context.MyTable;
    }

    private  MyEFContext InitContext()
    {
        var context = new MyEFContext();
        context.Database.Connection.ConnectionString = _connectionString;
        //context.Database.Log = SqlLog;
        return context;
    }

FromToRange は、FromプロパティとToプロパティを持つ単純な構造です。

2

そのため、このプロジェクトでは、まったく同じ問題を抱えていましたが、問題はforeachまたは.toList()にはありませんでした。これは、上記のエラーがスローされただけでなく、他の同等のエラーが多数スローされたという、いくつかの奇妙な状況を引き起こしました。

これは私達の修正でした:これを変更しました:

container.RegisterType<DataContext>().As<DbContext>().InstancePerLifetimeScope();
container.RegisterType<DbFactory>().As<IDbFactory>().SingleInstance();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();

に:

container.RegisterType<DataContext>().As<DbContext>().As<DbContext>();
container.RegisterType<DbFactory>().As<IDbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().As<IUnitOfWork>();//.InstancePerRequest();
2
VeldMuijz

私も同じ問題に直面していました。

これが原因と解決策です。

http://blogs.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transactions-and-sql-error-3997-3988-or-3983.aspx

挿入、更新などのデータ操作コマンドを実行する前に、それまでにアクティブだったすべてのSQLリーダーを閉じてください。

最も一般的なエラーは、dbからデータを読み込んで値を返す関数です。例えばisRecordExistのような関数です。

この場合、レコードが見つかった場合はすぐに関数から戻り、リーダーを閉じるのを忘れます。

1
Vinod T. Patil

私の場合は、EFを介してストアドプロシージャを呼び出したときに問題が発生し、その後SaveChangesがこの例外をスローしました。問題はプロシージャの呼び出しにあり、列挙子は破棄されませんでした。私は次のようにコードを修正しました:

public bool IsUserInRole(string username, string roleName, DataContext context)
{          
   var result = context.aspnet_UsersInRoles_IsUserInRoleEF("/", username, roleName);

   //using here solved the issue
   using (var en = result.GetEnumerator()) 
   {
     if (!en.MoveNext())
       throw new Exception("emty result of aspnet_UsersInRoles_IsUserInRoleEF");
     int? resultData = en.Current;

     return resultData == 1;//1 = success, see T-SQL for return codes
   }
}
1
Tomas Kubes

Foreachが原因でこのエラーが発生し、最初にエンティティをループ内に保存し、生成されたIDをループ内でさらに使用する必要がある場合、最も簡単な解決策は別のDBContextを使用してIdを返すエンティティを挿入して使用することです。このIdを外部コンテキストで

例えば

    using (var context = new DatabaseContext())
    {
        ...
        using (var context1 = new DatabaseContext())
        {
            ...
               context1.SaveChanges();
        }                         
        //get id of inserted object from context1 and use is.   
      context.SaveChanges();
   }
1
Hemant Sakta

私はパーティーにかなり遅れていますが、今日私は同じエラーに直面し、そして私がどうやって解決したかは簡単でした。私のシナリオは、入れ子になったfor-eachループの内側でDBトランザクションを作成していたこのコードに似ていました。

単一DBトランザクションはfor-eachループより少し時間がかかるため、以前のトランザクションが完了しないと新しいトラクションが例外をスローするため、解決策はfor-eachループ内に新しいオブジェクトを作成することです。 dbトランザクションを作成している場所。

上記のシナリオでは、解決策は次のようになります。

foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                {
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
                    if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
                    {
                        var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
                        foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
                        {
                            foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
                            {
                                if (targetProduct.alternateProductID == sourceProduct.AutoID)
                                {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found)
                            {
                                var newProduct = new RivWorks.Model.Negotiation.Product();
                                newProduct.alternateProductID = sourceProduct.AutoID;
                                newProduct.isFromFeed = true;
                                newProduct.isDeleted = false;
                                newProduct.SKU = sourceProduct.StockNumber;
                                company.Product.Add(newProduct);
                            }
                        }
                        _dbRiv.SaveChanges();  // ### THIS BREAKS ### //
                    }
                }
0
Usman

以下のコードは私のために働きます:

private pricecheckEntities _context = new pricecheckEntities();

...

private void resetpcheckedtoFalse()
{
    try
    {
        foreach (var product in _context.products)
        {
            product.pchecked = false;
            _context.products.Attach(product);
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }
    catch (Exception extofException)
    {
        MessageBox.Show(extofException.ToString());

    }
    productsDataGrid.Items.Refresh();
}
0
user2918896

あなたの問い合わせ可能なリストを.ToList()にしてもうまくいくはずです。

0

私はそれが古い質問であることを知っていますが、私は今日このエラーに直面しました。

そして、私は、データベーステーブルトリガがエラーを取得したときに、このエラーがスローされる可能性があることを見つけました。

ご参考までに、このエラーが発生したときにもテーブルトリガーを確認できます。

0
nadir

少し遅れていますが、このエラーもありました。私は何をどこで値がどこで更新するのかをチェックすることによって問題を解決しました。

私の質問が間違っていたこと、そして250以上の編集が保留されていることがわかりました。だから私は私のクエリを修正し、今それは正しく動作します。

だから私の状況では:クエリが返す結果をデバッグして、クエリのエラーをチェックするその後、クエリを修正してください。

これが将来の問題解決に役立つことを願っています。

0
Max