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


600

Зараз я отримую цю помилку:

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

Отже, велике запитання - як вирішити помилку транзакції, яку я отримую? Чи потрібно мені скидати та відтворювати свій контекст кожен раз через петлі (це не має для мене сенсу)?


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 () всмоктує їх усіх у пам'ять. Я зіткнувся з цією самою проблемою, і мені було цікаво, чи можливо наступне: a) Від'єднати сутність b) Створити новий ObjectContext і приєднати до нього відокремлену сутність. в) Зателефонуйте SaveChanges () на новий ObjectContext d) Від'єднайте об'єкт від нового ObjectContext e) Приєднайте його до старого ObjectContext
Abhijeet Patel

149
Проблема полягає в тому, що ви не можете телефонувати, 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 разів менших викликів оновлення бази даних. Звичайно, кожен із цих дзвінків займає більше часу, але ви все-таки вийдете вперед. Ваш пробіг може відрізнятися, але це було світами швидше для мене.

І це оточує виняток, який ви бачили.

EDIT Я переглянув це питання після запуску 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" і ви запитуєте. Де (x => x.ModifiedDate! = Null), і в кінці foreach ви встановлюєте значення для ModifiedDate. Таким чином ви не повторюєте половину записів, оскільки половина записів пропускається.
Арванд

На жаль, на величезних наборах даних ви отримаєте OutofMemoryException - див. Пояснення у сукупності великих даних Entity Framework, поза винятком пам'яті . Я описав, як оновлювати ваш контекст кожної партії в SqlException від Entity Framework - Нова транзакція не дозволена, оскільки в сесії працюють інші потоки
Майкл Фрейджім,

Я думаю, що це має спрацювати. var пропустити = 0; const int take = 100; Список <E zaposee> порожні; while ((emps = db.Eслуees.Skip (пропустити) .Take (take) .ToList ()). count> 0) {skip + = take; foreach (var emp in emps) {// Зробіть тут речі}} Я б сформулював це відповідь, але він буде захований нижче купи відповідей нижче, і це стосується цього питання.
jwize

123

Зараз ми опублікували офіційну відповідь на помилку, відкриту на 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();
    }
} 

6
Якщо ви скористаєтеся маршрутом транзакції, просто введення TransactionScope може не виправити це - не забудьте продовжити час очікування, якщо те, що ви робите, може зайняти багато часу - наприклад, якщо ви будете інтерактивно налагоджувати код, роблячи Виклик БД. Ось код, що розширює час очікування транзакції до години: використання (var транзакції = новий TransactionScope (TransactionScopeOption.Required, new 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
Ага! Принесіть контекст всередині кожного циклу! (pffft ... що я думав? ..) Спасибі!
Адам Кокс

18

Просто кладіть context.SaveChanges()після закінчення foreach(цикл).


Це кращий варіант, який я з’ясував у своєму випадку завдяки збереженню всередині foreach
Алмейда

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

             }

1
Це взагалі не є хорошою практикою. Ви не повинні виконувати SaveChanges так часто, якщо вам не потрібно, і ви точно не повинні "Завжди використовувати свій вибір як Список"
Dinerdo

@Dinerdo це дійсно залежить від сценарію. У моєму випадку у мене є 2 петлі переднього руху. Зовнішній один мав запит db як список. Наприклад, цей foreach обходить апаратні пристрої. Внутрішній foreach витягує кілька даних з кожного пристрою. Відповідно до вимоги, мені потрібно зберегти дані в базі даних після їх отримання з кожного пристрою по одному. Це не можливість зберігати всі дані в кінці процесу. Я зіткнувся з тією ж помилкою, але рішення mzonerz спрацювало.
jstuardo

@jstuardo Навіть з дозуванням?
Дінердо

@Dinerdo Я погоджуюся, що це не є хорошою практикою на філософському рівні. Однак існує кілька ситуацій, коли всередині циклу for for код викликає інший метод (скажімо, метод AddToLog ()), який включає виклик на db.SaveChanges () локально. У цій ситуації ви не можете реально контролювати дзвінок на db.Save Changes. У цьому випадку використання ToList () або подібної структури буде працювати, як запропонував mzonerz. Дякую!
А. Варма

На практиці це зашкодить вам більше, ніж допоможе. Я стою за те, що я сказав - ToList (), безумовно, не слід використовувати весь час, і збереження змін після кожного окремого предмета - це те, чого слід уникати, де це можливо, у високоефективній програмі. Це було б тимчасовим виправленням IMO. Незалежно від способу ведення журналу, ви також повинні в ідеалі скористатися буферизацією.
Дінердо

8

FYI: з книги та деяких рядків скориговано, тому що її стиль дійсний:

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

Якщо ви спробуєте викликати SaveChanges () перед тим, як всі дані будуть оброблені, ви зробите виняток "Нова транзакція заборонена, оскільки в сеансі працюють інші потоки". Виняток виникає через те, що SQL Server не дозволяє запускати нову транзакцію на з’єднанні, у якому відкритий SqlDataReader, навіть якщо декілька активних наборів запису (MARS) увімкнено рядком з'єднання (рядок підключення за замовчуванням EF дозволяє MARS)

Іноді краще зрозуміти, чому все відбувається ;-)


1
Хороший спосіб цього уникнути, коли у вас є відкритий читач, щоб відкрити другий і покласти ці операції на другий зчитувач. Це те, що вам може знадобитися, коли ви оновлюєте головний / деталі в рамках сутності. Ви відкриваєте перше з'єднання для основного запису, а друге - для записів деталей. якщо ти лише читаєш, проблем не повинно бути. проблеми виникають під час оновлення.
Герман Ван Дер Блом

Корисне пояснення. ти маєш рацію, добре зрозуміти, чому все відбувається.
Дов Міллер


5

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


3
У нас було подібне питання нещодавно: причиною в моєму випадку була SELECTзаява в збереженій процедурі, яка видала порожній набір результатів, і якщо цей набір результатів не був прочитаний, SaveChangesкинув це виключення.
n0rd

Те саме з непрочитаним результатом від СП, велике спасибі за натяк)
Pavel K

4

Ось ще два варіанти, за допомогою яких можна викликати 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 () таким чином. Ідея цього методу - захопити великий список цілих чисел, набагато ефективніше, ніж отримати великий список об’єктів 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()
}

Це чудова альтернатива, про яку я думав і робив, але це потрібно підтримувати. Примітка: 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();

Не могли б ви детальніше розглянути, на вашу думку, проблема? Ви вирішили це, створюючи новий Dbcontext кожен раз?
eran otzap

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 / ...
Michael Freidgeim

1

Я також стикався з тим же питанням.

Ось причина і рішення.

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

Переконайтесь, що перед запуском таких команд для маніпулювання даними, як вставки, оновлення, ви закрили всі попередні активні зчитувачі SQL.

Найчастіша помилка - це функції, які зчитують дані з db та повертають значення. Наприклад, такі функції, як isRecordExist.

У цьому випадку ми негайно повертаємося з функції, якщо знайшли запис і забули закрити читач.


7
Що означає "закрити читача" в Entity Framework? У запиті, як var result = від клієнта в myDb.Customers, де customer.Id == customerId select customer, немає видимого читача; повернути результат.FirstOrDefault ();
Ентоні

@Anthony Як говорять інші відповіді, якщо ви використовуєте EF для перерахунку над LINQ-запитом (IQueryable), базовий DataReader залишатиметься відкритим, поки не буде повторений останній рядок. Але хоча MARS є важливою функцією для включення в рядку з'єднання, проблема в ОП все ще не вирішена лише з 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();
}

2
Ласкаво просимо до SO! Спробуйте додати пояснення та / або посилання, що описують, чому це працює для вас. Відповіді, що стосуються лише коду, як правило, вважаються недоброякісними для SO.
codeMagic

1

У моєму випадку проблема з’явилася, коли я зателефонував до Stored Procedure через 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
   }
}

1

Ми почали бачити цю помилку "Нова транзакція заборонена, оскільки в сеансі працюють інші потоки" після переходу з EF5 на EF6.

Google привів нас сюди, але ми не дзвонимо SaveChanges() в цикл. Помилки виникали під час виконання збереженої процедури, використовуючи ObjectContext.ExecuteFunction всередині циклу foreach, що читається з БД.

Будь-який виклик до ObjectContext.ExecuteFunction завершує функцію транзакції. Початок транзакції, коли вже є відкритий зчитувач, викликає помилку.

Можна відключити обгортку SP в транзакції, встановивши наступний параметр.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

EnsureTransactionsForFunctionsAndCommandsОпція дозволяє SP працювати без створення своєї власної транзакції і помилка більше не піднімається.

DbContextConfiguration.EnsureTransactionForFunctionsAndCommands Properties


0

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

Проблема полягає в тому, що транзакція Single DB займає трохи більше часу, ніж для кожного циклу, тож коли попередня транзакція не завершена, то нова тяга кидає виняток, тому рішення полягає у створенні нового об'єкта в циклі for для кожного де ви здійснюєте транзакцію 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 ### //
                    }
                }

0

Я трохи запізнююся, але і у мене була ця помилка. Я вирішив проблему, перевіривши, де значень, де де оновлення.

Я з’ясував, що мій запит був неправильним і що там понад 250+ редагувань очікують на розгляд. Тому я виправив свій запит, і тепер він працює правильно.

Отже, в моїй ситуації: Перевірте запит на помилки, налагодивши результат, який запит повертає. Після цього виправте запит.

Сподіваюся, що це допомагає вирішити майбутні проблеми.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.