來自實體框架的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服務器上的數據庫中,並且每天通過自動提要進行更新。 alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

注 - 模型#1中帶紅色圓圈的項目是我用來“映射”到模型#2的字段。請忽略模型#2中的紅色圓圈:這是我現在回答的另一個問題。

注意:我仍然需要輸入一個isDeleted檢查,以便我可以從DB1中刪除它,如果它已經超出了我們客戶的庫存。

我想用這個特定的代碼做的就是將DB1中的公司與DB2中的客戶端連接起來,從DB2獲取產品列表,如果它不存在則將其插入DB1中。第一次通過應該是一個完整的庫存。每次運行之後都不會發生任何事情,除非飼料中的新庫存過夜。

所以最大的問題是 - 如何解決我遇到的交易錯誤?每次通過循環時我是否需要刪除並重新創建我的上下文(對我來說沒有意義)?

一般承認的答案

經過大量拔毛後,我發現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();
}

必須對您調用此方法的可查詢對象進行排序。這是因為實體框架僅在有序查詢上支持IQueryable<T>.Skip(int) ,當您考慮到不同範圍的多個查詢要求排序穩定時,這是有意義的。如果排序對您不重要,只需按主鍵排序,因為它可能具有聚簇索引。

此版本將以100個批次查詢數據庫。請注意,為每個實體調用SaveChanges()

如果您想SaveChanges()提高吞吐量,則應該不那麼頻繁地調用SaveChanges() 。使用這樣的代碼:

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

這導致數據庫更新調用減少了100倍。當然,這些電話中的每一個都需要更長的時間才能完成,但最終你還是會走在前面。你的里程可能會有所不同,但這對我來說世界更快。

它繞過你所看到的例外。

編輯我在運行SQL事件探查器後重新審視了這個問題並更新了一些內容以提高性能。對於任何感興趣的人,這裡有一些示例SQL,它顯示了DB創建的內容。

第一個循環不需要跳過任何東西,因此更簡單。

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合法嗎? 是的,了解原因