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

601

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

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-сервере. Модель № 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: это из другого моего вопроса, на который сейчас дан ответ.

Примечание: мне все еще нужно поставить проверку isDeleted, чтобы я мог мягко удалить ее из DB1, если она вышла из инвентаря нашего клиента.

Все, что я хочу сделать с этим конкретным кодом, - это связать компанию в DB1 с клиентом в DB2, получить их список продуктов от DB2 и вставить его в DB1, если его там еще нет. Первый раз должен быть полный запас инвентаря. Каждый раз, когда он запускается там, после того, как ничего не должно произойти, если за ночь не появилось новое количество материала.

Таким образом, большой вопрос - как решить ошибку транзакции, которую я получаю? Нужно ли мне каждый раз удалять и пересоздавать свой контекст через циклы (для меня это не имеет смысла)?

Кит Барроуз
источник
6
Это самый подробный вопрос, который я когда-либо видел.
9
Кто-нибудь еще пропускает хранимые процедуры?
Дэвид

Ответы:

690

После долгих выдергиваний из волос я обнаружил, что 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;
    // ...
}
Кит Барроуз
источник
14
Да, это тоже вызвало у меня головную боль. Я чуть не упал со стула, когда я нашел проблему! Я понимаю технические причины проблемы, но она не интуитивна и не помогает разработчику попасть в «яму успеха» blogs.msdn.com/brada/archive/2003/10/02/50420. aspx
Доктор Джонс
9
Разве это не плохо для производительности для больших наборов данных? Если у вас есть миллионы записей в таблице. ToList () высосет их всех в память. Я столкнулся с этой самой проблемой, и мне было интересно, возможно ли следующее: а) Отсоединить сущность б) Создать новый ObjectContext и присоединить к нему отсоединенную сущность. c) Вызовите SaveChanges () для нового ObjectContext. d) Отсоедините объект от нового ObjectContext. e) Присоедините его обратно к старому ObjectContext
Abhijeet Patel
150
Проблема в том, что вы не можете звонить, SaveChangesпока вы все еще извлекаете результаты из БД. Поэтому другое решение - просто сохранить изменения после завершения цикла.
Дрю Ноакс
4
Также, будучи укушенным, я добавил это в Microsoft Connect: connect.microsoft.com/VisualStudio/feedback/details/612369/… Не стесняйтесь голосовать за него.
Ян Мерсер
36
Наши разработчики, как правило, добавляют .ToList () к любому запросу LINQ, не задумываясь о последствиях. Это должно быть первое добавление .ToList () действительно полезно!
Марк
267

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

Эта версия будет запрашивать базу данных партиями по 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
Дрю Ноакс
источник
17
Спасибо. Ваше объяснение было гораздо более полезным, чем помеченное как «Ответ».
Вагнер да Силва
1
Это замечательно. только одно: если вы запрашиваете столбец и обновляете значение этого столбца, вам нужно быть осторожным с chunkNumber ++; , Допустим, у вас есть столбец «ModifiedDate», и вы запрашиваете .Where (x => x.ModifiedDate! = Null), и в конце каждого foreach вы устанавливаете значение для ModifiedDate. Таким образом, вы не перебираете половину записей, потому что половина записей пропускается.
Арванд
К сожалению, для огромных наборов данных вы получите OutofMemoryException - см. Объяснение в большом наборе данных Entity Framework, исключение нехватки памяти . Я описал, как обновлять ваш контекст в каждом пакете в SqlException из Entity Framework. - Новая транзакция не разрешена, поскольку в сеансе выполняются другие потоки.
Майкл Фрейдгейм,
Я думаю, что это должно работать. var skip = 0; const int take = 100; List <Employee> emps; while ((emps = db.Employees.Skip (skip) .Take (take) .ToList ()). Count> 0) {skip + = take; foreach (var emp in emps) {// Делайте что-то здесь}} Я бы сформулировал этот ответ, но он был бы похоронен ниже груды ответов ниже, и это относится к этому вопросу.
jwize
124

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

Эта ошибка связана с тем, что 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();
    }
} 
Марк Стаффорд - MSFT
источник
6
Если вы выбираете маршрут транзакции, простое добавление TransactionScope может не исправить его - не забудьте продлить время ожидания, если то, что вы делаете, может занять много времени - например, если вы будете интерактивно отлаживать код, делая Вызов БД. Вот код, увеличивающий время ожидания транзакции до часа: использование (var транзакция = новый TransactionScope (TransactionScopeOption.Required, новый TimeSpan (1, 0, 0)))
Крис Москини
Я столкнулся с этой ошибкой в ​​самый первый раз, когда я отошел от «пути обучения» в собственный реальный пример! Для меня, однако, чем проще решение, СОХРАНИТЕ ПОСЛЕ ИТЕРАЦИИ, тем лучше! (Я думаю, что в 99% случаев это так, и только 1% действительно ДОЛЖЕН выполнить базу данных, сохраняя ВНУТРИ цикла)
паук
Валовой. Я только что наткнулся на эту ошибку. Очень противный Второе предложение сработало как очарование для меня, а также переместил мои SaveChanges в цикл. Я думал, что сохранение изменений вне цикла было лучше для пакетных изменений. Но все в порядке. Я думаю, нет ?! :(
Мистер Янг
У меня не работал .NET 4.5. Когда я использовал TransactionScope, я получил следующую ошибку: «Ошибка основного поставщика в EnlistTransaction. {» Менеджер транзакций партнера отключил поддержку удаленных / сетевых транзакций. (Исключение из HRESULT: 0x8004D025) "}". Я заканчиваю тем, что выполняю работу вне итерации.
Диганта Кумар
Использование TransactionScope опасно, потому что таблица заблокирована на время всей транзакции.
Майкл Фрейдгейм
19

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

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

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

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

Эдгардо Пичардо С.
источник
2
Я подумал, что было бы интересно увидеть «обычную систему баз данных (RDMS)» здесь
Dinerdo
1
Это кажется неправильным, поскольку многократный вызов SaveChanges - это нормально в 90% случаев в EF.
Pxtl
Кажется, что повторный вызов SaveChanges - это нормально, если только цикл foreach не выполняет итерацию по объекту db.
Kerbasaurus
1
Ага! Принесите контекст внутри для каждого цикла! (пффф ... о чем я думал? ..) Спасибо!
Адам Кокс
18

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

Маджид
источник
Это лучший вариант, который я обнаружил в моем случае из-за экономии внутри foreach
Almeida
2
Это не всегда вариант.
Pxtl
9

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

Например:

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

             }
mzonerz
источник
1
Это не очень хорошая практика. Вы не должны выполнять SaveChanges так часто, если вам это не нужно, и вам определенно не следует «Всегда использовать свой выбор в качестве списка»
Dinerdo
@Dinerdo это действительно зависит от сценария. В моем случае у меня есть 2 цикла foreach. У внешнего был список запросов в виде списка. Например, этот foreach пересекает аппаратные устройства. Внутренний foreach извлекает несколько данных с каждого устройства. Согласно требованию, мне нужно сохранить в базу данных данные после того, как они будут извлечены с каждого устройства по одному. Это не возможность сохранить все данные в конце процесса. Я столкнулся с той же ошибкой, но решение mzonerz сработало.
jstuardo
@jstuardo Даже с дозированием?
Динердо
@Dinerdo Я согласен, что это не очень хорошая практика на философском уровне. Однако существует несколько ситуаций, когда внутри цикла for код вызывает другой метод (скажем, метод AddToLog ()), который включает локальный вызов db.SaveChanges (). В этой ситуации вы не можете контролировать вызов db.Save Changes. В этом случае использование ToList () или аналогичной структуры будет работать так, как это предлагает mzonerz. Спасибо!
А. Варма
На практике это повредит вам больше, чем поможет. Я придерживаюсь того, что я сказал - ToList () определенно не должен использоваться все время, и сохранение изменений после каждого отдельного элемента - это то, чего следует избегать везде, где это возможно, в высокопроизводительном приложении. Это будет временное исправление ИМО. Какой бы метод записи у вас ни был, в идеале также следует использовать буферизацию.
Динердо
8

К вашему сведению: из книги и некоторых строк откорректировано, потому что все еще действует:

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

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

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

Герман ван дер Блом
источник
1
Хороший способ избежать этого - это когда у вас открыт ридер, чтобы открыть второй и поместить эти операции во второй ридер. Это то, что вам может понадобиться при обновлении master / details в структуре сущностей. Вы открываете первое соединение для основной записи, а второе - для подробных записей. если вы только читаете, проблем не должно быть. проблемы возникают во время обновления.
Херман ван дер Блом
Полезное объяснение. Вы правы, хорошо понимать, почему что-то происходит.
Дов Миллер
8

Создайте свои списки для запросов в .ToList (), и он должен работать нормально.

Войцех Северин
источник
1
Пожалуйста, предоставьте пример, а не просто разместите решение.
Ронни Остинг
5

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

MikeKulls
источник
3
Недавно возникла похожая проблема: причиной в моем случае была SELECTинструкция в хранимой процедуре, которая выдает пустой набор результатов, и если этот набор результатов не был прочитан, SaveChangesвыдается это исключение.
2013 года
То же самое с непрочитанным результатом от SP, большое спасибо за подсказку)
Павел К
4

Вот еще 2 параметра, которые позволяют вам вызывать 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, но выбрать только идентификаторы. Затем выполните итерацию по списку идентификаторов (предположительно, 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()
}
jjspierx
источник
Это отличная альтернатива, о которой я думал и делал, но за это нужно проголосовать. Примечание: i) вы можете выполнять итерацию как перечислимое, что хорошо для очень больших множеств; ii) Вы можете использовать команду NoTracking, чтобы избежать проблем с загрузкой такого количества записей (если это ваш сценарий); iii) Мне действительно нравится опция только с первичным ключом - она ​​очень умная, потому что вы загружаете в память намного меньше данных, но вы не имеете дело с Take / Skip для потенциально динамического базового набора данных.
Тодд
4

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

Таким образом, в проекте, где у меня была точно такая же проблема, проблема была не в том, 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();
VeldMuijz
источник
Не могли бы вы уточнить, в чем, по вашему мнению, была проблема? Вы решили это, создавая новый Dbcontext каждый раз?
Эран Отзап
2

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

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

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

надир
источник
2

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

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

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

Рекомендуется заново создать контекст для каждого пакета.

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

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

  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.

Майкл Фрейдгейм
источник
Я не увидел, как вы «обновляли» свой контекст. Похоже, вы просто создаете новый контекст для каждого куска.
Suncat2000
@ Suncat2000, вы правы, контекст должен быть недолговечным объектом stackoverflow.com/questions/43474112/…
Майкл Фрейдгейм
2

Мы начали видеть эту ошибку «Новая транзакция не разрешена, потому что в сеансе запущены другие потоки» после миграции с EF5 на EF6.

Google привел нас сюда, но мы не звоним SaveChanges()в цикле. Ошибки возникали при выполнении хранимой процедуры с использованием ObjectContext.ExecuteFunction внутри цикла foreach, считывающего из БД.

Любой вызов ObjectContext.ExecuteFunction оборачивает функцию в транзакцию. Начало транзакции, когда уже открыт читатель, вызывает ошибку.

Можно отключить упаковку SP в транзакции, установив следующую опцию.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

Эта EnsureTransactionsForFunctionsAndCommandsопция позволяет SP работать без создания собственной транзакции, и ошибка больше не возникает.

Свойство DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands

JamPickle
источник
1

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

Вот причина и решение.

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

Убедитесь, что перед запуском команд обработки данных, таких как вставки, обновления, вы закрыли все предыдущие активные программы чтения SQL.

Наиболее распространенная ошибка - это функции, которые читают данные из БД и возвращают значения. Например, для таких функций, как isRecordExist.

В этом случае мы немедленно возвращаемся из функции, если нашли запись и забываем закрыть читатель.

Винод Т. Патил
источник
7
Что означает «закрыть читателя» в Entity Framework? В запросе нет видимого читателя, такого как var result = from customer в myDb.Customers, где customer.Id == customerId выбрать клиента; вернуть результат. FirstOrDefault ();
Энтони
@Anthony Как говорят другие ответы, если вы используете EF для перечисления по запросу LINQ (IQueryable), базовый DataReader будет оставаться открытым до тех пор, пока не будет повторен последний ряд. Но хотя MARS является важной функцией, включаемой в строку подключения, проблема в OP по-прежнему не решается только с помощью MARS. Проблема заключается в попытке сохранить изменения, пока базовый DataReader еще открыт.
Тодд
1

Код ниже работает для меня:

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();
}
user2918896
источник
2
Добро пожаловать на ТАК! Попробуйте добавить объяснение и / или ссылки, объясняющие, почему это работает для вас. Ответы только на код, как правило, считаются не очень хорошими для SO.
codeMagic
1

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

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

Проблема заключается в том, что транзакция с одной БД занимает немного больше времени, чем цикл for-each, поэтому, как только более ранняя транзакция не завершена, новая тяга вызывает исключение, поэтому решение заключается в создании нового объекта в цикле for-each где вы делаете транзакцию БД.

Для вышеупомянутых сценариев решение будет таким:

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 ### //
                    }
                }
Усман
источник
0

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

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

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

Надеюсь, это поможет решить будущие проблемы.

Максимум
источник