SqlException из Entity Framework-новая транзакция не разрешена, поскольку в сеансе запущены другие потоки

в настоящее время я получаю эту ошибку:

19 ответов


после много вытягивать из волос я обнаружил что 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();
}

объект queryable, вызываемый этим методом, должен быть упорядочен. это потому, что 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 раз меньше вызовов обновления базы данных. Конечно, каждый из этих вызовов занимает больше времени, но вы все равно выходите вперед в конце. Ваш пробег может варьироваться, но для меня это было намного быстрее.

и он обходит исключение, которое вы были видение.

редактировать я вернулся к этому вопросу после запуска SQL Profiler и обновил несколько вещей, чтобы улучшить производительность. Для всех, кто заинтересован, вот пример 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

теперь мы опубликовали официальный ответ на ошибка открыта при подключении. Обходные пути, которые мы рекомендуем, следующие:

эта ошибка вызвана созданием Entity Framework неявной транзакции во время вызова SaveChanges (). Лучший способ обойти ошибку-использовать другой шаблон (т. е. не сохранять во время чтения) или явно объявить транзакцию. Вот три возможных решения:

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

просто поставить context.SaveChanges() после окончания вашего foreach(петля).


действительно, Вы не можете сохранить изменения внутри foreach цикл в C# с использованием Entity Framework.

context.SaveChanges() метод действует как фиксация в обычной системе баз данных (RDMS).

просто внесите все изменения (которые Entity Framework будет кэшировать), а затем сохраните их все сразу, вызвав SaveChanges() после цикла (вне его), как база данных команда commit.

это работает, если вы можете сохранить все изменения сразу.


Я получал эту же проблему, но в другой ситуации. У меня был список предметов в списке. Пользователь может щелкнуть элемент и выбрать удалить, но я использую сохраненный proc для удаления элемента, потому что в удалении элемента задействовано много логики. Когда я вызываю сохраненный proc, удаление работает нормально, но любой будущий вызов SaveChanges вызовет ошибку. Мое решение состояло в том, чтобы вызвать сохраненный proc за пределами EF, и это сработало нормально. Почему-то когда я вызываю хранимую proc, используя в эф способ делать вещи, он оставляет что-то открыть.


FYI: из книги и некоторых строк, скорректированных, потому что его стиль действителен:

вызов метода SaveChanges () начинает транзакцию, которая автоматически откатывает все изменения, сохраненные в базе данных, если исключение происходит до завершения итерации; в противном случае транзакция фиксирует. Может возникнуть соблазн применить этот метод после каждого обновления или удаления сущности, а не после завершения итерации, особенно при обновлении или удалении большого количества сущности.

при попытке вызвать SaveChanges () до обработки всех данных возникает исключение "новая транзакция не разрешена, поскольку в сеансе выполняются другие потоки". Исключение возникает из-за того, что SQL Server не разрешает запускать новую транзакцию в соединении с открытым SqlDataReader, даже если несколько активных наборов записей (MARS) включены строкой подключения (строка подключения по умолчанию EF включает MARS)

иногда его лучше понять, почему все происходит ; -)


вот еще 2 варианта, которые позволяют вызывать SaveChanges () в каждом цикле.

первый вариант-использовать один DBContext для создания объектов списка для итерации, а затем создать второй DBContext для вызова SaveChanges () on. Вот пример:

//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 (предположительно int) и получите объект соответствующий каждому int и вызывающий SaveChanges () таким образом. Идея этого метода заключается в захвате большого списка целых чисел, намного эффективнее, чем получение большого списка объектов БД и вызов .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()
}

всегда используйте свой выбор в качестве списка

например:

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();

             }

Итак, в проекте у меня была эта же проблема, проблема не была в foreach или .toList() Это было на самом деле в конфигурации AutoFac, которую мы использовали. Это создало некоторые странные ситуации, когда вышеприведенная ошибка была выброшена, но также была выброшена куча других эквивалентных ошибок.

Это было наше исправление: Изменил это:

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();

Я также столкнулся с той же проблемой.


в моем случае проблема возникла, когда я вызвал хранимую процедуру через 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
   }
}

Мне нужно было прочитать огромный ResultSet и обновить некоторые записи в таблице. Я попытался использовать куски, как предложено в Обратил Ноукс ' s ответ.

к сожалению, после 50000 записей я получил OutofMemoryException. Ответ Entity framework большой набор данных, исключение из памяти поясняет, что

EF создает вторую копию данных, которая используется для обнаружения изменений (так что оно может упорствовать изменения к база данных.) EF держит этот второй набор на всю жизнь контекста и его этот набор, который запускает вас памяти.

рекомендуется обновлять контекст каждой партии.

поэтому я извлек минимальные и максимальные значения первичного ключа - таблицы имеют первичные ключи как автоматические инкрементные целые числа.Затем я извлек из базы данных куски записей, открыв контекст для каждого куска. После обработки контекста chunk закрывает и освобождает память. Это гарантирует, что использование памяти не растет.

Ниже приведен фрагмент моего кода:

  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.


приведенный ниже код работает для меня:

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

Я сильно опоздал на вечеринку, но сегодня я столкнулся с той же ошибкой, и как я решил, было просто. Мой сценарий был похож на этот данный код, я делал транзакции DB внутри вложенных циклов for-each.

проблема заключается в том, что одна транзакция DB занимает немного больше времени, чем для каждого цикла, поэтому, как только более ранняя транзакция не завершена, новая тяга создает исключение, поэтому решение заключается в создании нового объекта в цикле 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 ### //
                    }
                }

Я немного опоздал, но у меня тоже была эта ошибка. Я решил проблему, проверив, что где значения, что где обновление.

Я узнал, что мой запрос был неправильным и что там, где более 250+ редактирует ожидание. Поэтому я исправил свой запрос, и теперь он работает правильно.

так что в моей ситуации: Проверьте запрос на наличие ошибок, отладив результат, который возвращает запрос. После этого исправьте запрос.

надеюсь, что это помогает в решении проблем в будущем.


Я знаю, что это старый вопрос, но я столкнулся с этой ошибкой сегодня.

и я обнаружил, что эта ошибка может быть вызвана, когда триггер таблицы базы данных получает ошибку.

для вашей информации вы также можете проверить триггеры таблиц, когда получите эту ошибку.


Если вы получаете эту ошибку из-за foreach, и вам действительно нужно сначала сохранить одну сущность внутри цикла и использовать сгенерированную идентичность дальше в цикле, как это было в моем случае, самым простым решением является использование другого 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();
   }

создание ваших запрашиваемых списков .Список() и он должен работать нормально.