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

c# entity-framework inversion-of-control transactions

質問

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

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 - このモデルは私たちのDev Server上のデータベースにあります。 モデル#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に挿入することです。初回は、在庫を完全に引き出す必要があります。毎晩新しい在庫がフィードに入ってこない限り、何も起こらないようにしてそこで実行されるたびに。

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

受け入れられた回答

髪を引っ張った後、 foreachループが原因であることが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;
    // ...
}

人気のある回答

すでに確認したように、アクティブなリーダーを介してデータベースからまだ描画している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)しかサポートしていないためです。これは、範囲が異なる複数のクエリで順序付けを安定させる必要がある場合に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になります。もちろん、これらの通話はそれぞれ完了するまでに時間がかかりますが、最終的にはまだ先に進みます。あなたの走行距離は異なるかもしれませんが、これは私にとって世界の方が早かったです。

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

編集私はSQLプロファイラーを実行した後にこの質問に再び触れ、パフォーマンスを改善するためにいくつかのことを更新しました。興味のある人のために、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


Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ