来自实体框架的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() 。使用这样的代码:

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


许可下: CC-BY-SA with attribution
不隶属于 Stack Overflow
这个KB合法吗? 是的,了解原因
许可下: CC-BY-SA with attribution
不隶属于 Stack Overflow
这个KB合法吗? 是的,了解原因