我目前得到这个错误:

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服务器的数据库中,每天通过自动馈送进行更新。alt文本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或。tolist(),它实际上是在我们使用的AutoFac配置。 这造成了一些奇怪的情况,上面的错误被抛出,但也抛出了一堆其他等效的错误。

这是我们的解决方案: 改变:

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

To:

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

其他回答

下面是另外两个选项,允许您在for每个循环中调用SaveChanges()。

第一个选项是使用一个DBContext来生成要遍历的列表对象,然后创建第二个DBContext来调用SaveChanges()。这里有一个例子:

//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()
    }
}

第二个选项是从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()
}

只需将context.SaveChanges()放在foreach(循环)的末尾。

我也面临着同样的问题。

这里是原因和解决方法。

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

在触发数据操作命令(如插入、更新)之前,请确保您已经关闭了所有以前的活动SQL读取器。

最常见的错误是从db读取数据并返回值的函数。 例如isRecordExist这样的函数。

在这种情况下,如果我们找到记录并且忘记关闭读取器,我们将立即从函数返回。

供你参考:来自一本书,一些行调整,因为它仍然有效:

调用SaveChanges()方法将启动一个事务,如果在迭代完成之前发生异常,该事务将自动回滚持久化到数据库的所有更改;否则事务将提交。您可能倾向于在每次实体更新或删除之后应用该方法,而不是在迭代完成之后,特别是当您更新或删除大量实体时。

如果在处理完所有数据之前尝试调用SaveChanges(),则会引发“不允许新事务,因为会话中正在运行其他线程”异常。出现异常是因为SQL Server不允许在一个打开了SqlDataReader的连接上启动一个新事务,即使连接字符串启用了多个活动记录集(MARS) (EF的默认连接字符串启用了MARS)。

有时候理解事情为什么会发生会更好;-)

正如您已经确定的那样,您不能在仍然通过活动读取器从数据库中提取数据的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 Profiler后,我重新审视了这个问题,并更新了一些东西来提高性能。对于感兴趣的人,这里有一些示例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