SqlException de Entity Framework – No se permite nueva transacción porque hay otros subprocesos que se ejecutan en la sesión

Actualmente estoy recibiendo este error:

System.Data.SqlClient.SqlException: no se permite la nueva transacción porque hay otros subprocesos que se ejecutan en la sesión.

mientras ejecuta este código:

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

Modelo n. ° 1: este modelo se encuentra en una base de datos en nuestro servidor Dev. Modelo n.º 1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png

Modelo n. ° 2: este modelo se encuentra en una base de datos en nuestro Prod Server y se actualiza cada día mediante feeds automáticos. texto alternativo http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Nota: los elementos encerrados en un círculo rojo en el Modelo # 1 son los campos que uso para “mapear” al Modelo # 2. Por favor, ignore los círculos rojos en el Modelo n. ° 2: eso es de otra pregunta que tuve que ahora se responde.

Nota: Todavía necesito ingresar un cheque eliminado para poder eliminarlo de DB1 si se ha salido del inventario de mi cliente.

Todo lo que quiero hacer, con este código en particular, es conectar una empresa en DB1 con un cliente en DB2, obtener su lista de productos de DB2 e INSERTARla en DB1 si aún no está allí. La primera vez debería ser una extracción completa de inventario. Cada vez se ejecuta allí después de que nada debería suceder a menos que haya un nuevo inventario en el feed durante la noche.

Entonces, la gran pregunta: ¿cómo puedo resolver el error de transacción que estoy obteniendo? ¿Debo dejar caer y recrear mi contexto cada vez que paso por los bucles (no tiene sentido para mí)?

Después de mucho quitarme el pelo descubrí que los bucles foreach eran los culpables. Lo que debe suceder es llamar a EF pero devolverlo a un IList de ese tipo de destino y luego hacer un loop en el IList .

Ejemplo:

 IList 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; // ... } 

Como ya ha identificado, no puede guardar desde dentro de un foreach que aún se está extrayendo de la base de datos a través de un lector activo.

Llamar a ToList() o ToArray() está bien para conjuntos de datos pequeños, pero cuando tiene miles de filas, consumirá una gran cantidad de memoria.

Es mejor cargar las filas en trozos.

 public static class EntityFrameworkUtil { public static IEnumerable QueryInChunksOf(this IQueryable queryable, int chunkSize) { return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk); } public static IEnumerable QueryChunksOfSize(this IQueryable 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++; } } } 

Teniendo en cuenta los métodos de extensión anteriores, puede escribir su consulta de esta manera:

 foreach (var client in clientList.OrderBy(c => c.Id).QueryInChunksOf(100)) { // do stuff context.SaveChanges(); } 

El objeto consultable al que llama este método debe estar ordenado. Esto se debe a que Entity Framework solo admite IQueryable.Skip(int) en consultas ordenadas, lo que tiene sentido si IQueryable.Skip(int) en cuenta que las consultas múltiples para diferentes rangos requieren que el orden sea estable. Si el pedido no es importante para usted, simplemente solicite por clave principal ya que es probable que tenga un índice agrupado.

Esta versión consultará la base de datos en lotes de 100. Tenga en cuenta que se llama a SaveChanges() para cada entidad.

Si desea mejorar su rendimiento dramáticamente, debe llamar SaveChanges() menos frecuencia. Use un código como este en su lugar:

 foreach (var chunk in clientList.OrderBy(c => c.Id).QueryChunksOfSize(100)) { foreach (var client in chunk) { // do stuff } context.SaveChanges(); } 

Esto da como resultado 100 veces menos llamadas de actualización de base de datos. Por supuesto, cada una de esas llamadas lleva más tiempo para completarse, pero al final se gana la delantera. Su kilometraje puede variar, pero esto fue mucho más rápido para mí.

Y supera la excepción que estabas viendo.

EDITAR Revisé esta pregunta después de ejecutar SQL Profiler y actualicé algunas cosas para mejorar el rendimiento. Para cualquiera que esté interesado, aquí hay un ejemplo de SQL que muestra lo que crea el DB.

El primer ciclo no necesita omitir nada, por lo que es más simple.

 SELECT TOP (100) -- the chunk size [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], FROM [dbo].[Clients] AS [Extent1] ORDER BY [Extent1].[Id] ASC 

Las llamadas posteriores deben omitir fragmentos de resultados anteriores, por lo que se introduce el uso de 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 

Ahora hemos publicado una respuesta oficial al error abierto en Connect . Las soluciones alternativas que recomendamos son las siguientes:

Este error se debe a que Entity Framework crea una transacción implícita durante la llamada SaveChanges (). La mejor manera de evitar el error es usar un patrón diferente (es decir, no guardar mientras se está leyendo) o declarar explícitamente una transacción. Aquí hay tres soluciones posibles:

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

Simplemente ponga context.SaveChanges() después del final de su foreach (loop).

Estaba teniendo el mismo problema pero en una situación diferente. Tenía una lista de artículos en un cuadro de lista. El usuario puede hacer clic en un elemento y seleccionar eliminar, pero estoy usando un proceso almacenado para eliminar el elemento porque hay mucha lógica involucrada en eliminar el elemento. Cuando llamo al proceso almacenado, la eliminación funciona bien, pero cualquier llamada futura a SaveChanges causará el error. Mi solución fue llamar al proceso almacenado fuera de EF y funcionó bien. Por alguna razón, cuando llamo al proceso almacenado usando la forma EF de hacer las cosas, deja algo abierto.

FYI: de un libro y algunas líneas ajustadas porque es aún válido:

La invocación del método SaveChanges () inicia una transacción que revierte automáticamente todos los cambios que persisten en la base de datos si se produce una excepción antes de que finalice la iteración; de lo contrario, la transacción se compromete. Es posible que tenga la tentación de aplicar el método después de cada actualización o eliminación de la entidad, en lugar de una vez que se complete la iteración, especialmente cuando está actualizando o eliminando cantidades masivas de entidades.

Si intenta invocar SaveChanges () antes de que se hayan procesado todos los datos, incurrirá en una excepción de “Nueva transacción no está permitida porque hay otros subprocesos ejecutándose en la sesión”. La excepción se produce porque SQL Server no permite iniciar una nueva transacción en una conexión que tiene un SqlDataReader abierto, incluso con múltiples conjuntos de registros activos (MARS) habilitados por la cadena de conexión (la cadena de conexión predeterminada de EF habilita a MARS)

A veces es mejor entender por qué están sucediendo cosas 😉

Aquí hay otras 2 opciones que le permiten invocar SaveChanges () en a para cada ciclo.

La primera opción es usar un DBContext para generar sus objetos de lista para iterar, y luego crear un segundo DBContext para llamar a SaveChanges (). Aquí hay un ejemplo:

 //Get your IQueryable list of objects from your main DBContext(db) IQueryable 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() } } 

La segunda opción es obtener una lista de objetos de base de datos de DBContext, pero solo seleccionar los id. Y luego recorra la lista de id (presumiblemente un int) y obtenga el objeto correspondiente a cada int, e invoque SaveChanges () de esa manera. La idea detrás de este método es tomar una gran lista de enteros, es mucho más eficiente que obtener una gran lista de objetos db y llamar a .ToList () en todo el objeto. Aquí hay un ejemplo de este método:

 //Get the list of objects you want from your DBContext, and select just the Id's and create a list List 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() } 

Siempre use su selección como lista

P.ej:

 var tempGroupOfFiles = Entities.Submited_Files.Where(r => r.FileStatusID == 10 && r.EventID == EventId).ToList(); 

Luego pasa por la colección mientras guardas los cambios

  foreach (var item in tempGroupOfFiles) { var itemToUpdate = item; if (itemToUpdate != null) { itemToUpdate.FileStatusID = 8; itemToUpdate.LastModifiedDate = DateTime.Now; } Entities.SaveChanges(); } 

De hecho, no puede guardar los cambios dentro de un ciclo foreach en C # utilizando Entity Framework.

context.SaveChanges() método context.SaveChanges() actúa como una confirmación en un sistema de base de datos regular (RDMS).

Simplemente realice todos los cambios (que Entity Framework almacenará en caché) y luego guárdelos todos a la vez llamando a SaveChanges() después del bucle (fuera de él), como un comando de confirmación de base de datos.

Esto funciona si puede guardar todos los cambios a la vez.

Entonces, en el proyecto si tuviera este mismo problema, el problema no estaba en el foreach o .toList() , en realidad estaba en la configuración de AutoFac que usamos. Esto creó algunas situaciones extrañas donde se arrojó el error anterior, pero también se lanzaron un montón de otros errores equivalentes.

Esta fue nuestra solución: Cambié esto:

 container.RegisterType().As().InstancePerLifetimeScope(); container.RegisterType().As().SingleInstance(); container.RegisterType().As().InstancePerRequest(); 

A:

 container.RegisterType().As().As(); container.RegisterType().As().As().InstancePerLifetimeScope(); container.RegisterType().As().As();//.InstancePerRequest(); 

Yo también estaba enfrentando el mismo problema.

Aquí está la causa y la solución.

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

Antes de ejecutar comandos de manipulación de datos, como inserciones y actualizaciones, asegúrese de haber cerrado todos los lectores de SQL activos anteriores.

El error más común es funciones que leen datos de db y devuelven valores. Por ejemplo, funciones como isRecordExist.

En este caso, inmediatamente regresaremos de la función si encontramos el registro y olvidamos cerrar el lector.

En mi caso, el problema apareció cuando llamé a Stored Procedure vía EF y luego SaveChanges lanzó esta excepción. El problema fue al llamar al procedimiento, el enumerador no fue eliminado. Arreglé el código de la siguiente manera:

 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 } } 

Necesitaba leer un ResultSet enorme y actualizar algunos registros en la tabla. Traté de usar fragmentos como se sugiere en la respuesta de Drew Noakes .

Lamentablemente, después de 50000 registros obtuve OutofMemoryException. La respuesta del conjunto de datos grande del marco de Entity, excepción de falta de memoria , explica que

EF crea una segunda copia de datos que utiliza para la detección de cambios (para que pueda persistir en los cambios de la base de datos). EF mantiene este segundo conjunto durante toda la vida del contexto, y es este conjunto lo que te deja sin memoria.

La recomendación es renovar el contexto de cada lote.

Así que he recuperado los valores Mínimo y Máximo de la clave primaria, las tablas tienen claves primarias como enteros incrementales automáticos. Luego recuperé de la base de datos los registros abriendo el contexto para cada fragmento. Después del procesamiento, el contexto del fragmento se cierra y libera la memoria. Asegura que el uso de la memoria no está creciendo.

Debajo hay un fragmento de mi código:

  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 GetminMaxIds() { var minMaxIds = new FromToRange(); 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 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 es una estructura simple con propiedades From y To.

El siguiente código funciona para mí:

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

Llego muy tarde a la fiesta, pero hoy enfrenté el mismo error y la forma en que resolví fue simple. Mi escenario era similar a este código dado. Estaba haciendo transacciones de DB dentro de nesteds para cada bucle.

El problema es que una transacción Single DB tarda un poco más de tiempo que for-each loop, por lo que una vez que la transacción anterior no está completa, la nueva traction arroja una excepción, por lo que la solución es crear un nuevo objeto en el ciclo for-each donde estás haciendo una transacción db.

Para los escenarios mencionados anteriormente, la solución será la siguiente:

 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 ### // } } 

Estoy un poco tarde, pero también tuve este error. Resolví el problema comprobando dónde estaban los valores que estaban actualizando.

Descubrí que mi consulta era incorrecta y que había más de 250 ediciones pendientes. Así que corregí mi consulta, y ahora funciona correctamente.

Por lo tanto, en mi situación: compruebe la consulta en busca de errores depurando el resultado que devuelve la consulta. Después de eso, corrige la consulta.

Espero que esto ayude a resolver problemas futuros.

Sé que es una vieja pregunta, pero hoy enfrenté este error.

y descubrí que este error se puede producir cuando un desencadenante de tabla de base de datos recibe un error.

para su información, puede verificar los cuadros desencadenantes también cuando obtiene este error.

Si obtiene este error debido a foreach y realmente necesita guardar una entidad primero dentro del ciclo y usar la identidad generada más en bucle, como en mi caso, la solución más fácil es usar otro DBContext para insertar la entidad que devolverá Id y usará este Id en contexto externo

Por ejemplo

  using (var context = new DatabaseContext()) { ... using (var context1 = new DatabaseContext()) { ... context1.SaveChanges(); } //get id of inserted object from context1 and use is. context.SaveChanges(); } 

Haciendo tus listas consultables a .ToList () y debería funcionar bien.