webapiconfig unity mvc injection httpconfiguration dependency c# entity-framework transactions inversion-of-control

unity - web api en c#



SqlException de Entity Framework: la nueva transacción no está permitida porque hay otros subprocesos que se ejecutan en la sesión (19)

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

Este error se debe a que Entity Framework crea una transacción implícita durante la llamada a SaveChanges (). La mejor manera de evitar el error es usar un patrón diferente (es decir, no guardar mientras se lee) 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(); } }

Actualmente estoy recibiendo este error:

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

mientras se 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 de nuestro servidor de desarrollo. Modelo # 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 de nuestro Prod Server y se actualiza todos los días mediante feeds automáticos. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Nota: los elementos con un círculo rojo en el Modelo n. ° 1 son los campos que uso para "mapear" al Modelo n. ° 2. Por favor ignore los círculos rojos en el Modelo # 2: es de otra pregunta que tengo y que ahora está respondida.

Nota: Todavía necesito realizar una verificación isDeleted para poder borrarlo de la DB1 si se ha salido del inventario de nuestro cliente.

Todo lo que quiero hacer, con este código en particular, es conectar una compañía 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 que pase debe ser un tirón completo de inventario. Cada vez que se ejecuta allí, no debe ocurrir nada a menos que se ingrese un nuevo inventario en el feed durante la noche.

Entonces, la gran pregunta: ¿cómo resuelvo el error de transacción que recibo? ¿Necesito soltar y recrear mi contexto cada vez que pasa por los bucles (no tiene sentido para mí)?


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

La primera opción es usar un DBContext para generar los objetos de la 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<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() } }

La segunda opción es obtener una lista de objetos de base de datos del DBContext, pero seleccionar solo los ID. Y luego itere a través de la lista de identificadores (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 lista grande de enteros, es mucho más eficiente que obtener una lista grande 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<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() }


Así que en el proyecto en el que tuve exactamente el mismo problema, el problema no estaba en el foreach o en el .toList() sino en la configuración de AutoFac que usamos. Esto creó algunas situaciones extrañas donde se lanzó el error anterior, pero también se lanzaron un montón de otros errores equivalentes.

Esta fue nuestra solución: Cambiado esto:

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

A:

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


Como ya se identificó, no puede guardar desde un foreach que aún está dibujando desde la base de datos a través de un lector activo.

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

Es mejor cargar las filas en trozos.

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

Dados 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 ser ordenado. Esto se debe a que Entity Framework solo admite IQueryable<T>.Skip(int) en las consultas ordenadas, lo que tiene sentido cuando se considera que las consultas múltiples para diferentes rangos requieren que el orden sea estable. Si el orden no es importante para usted, solo ordene 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 a SaveChanges() menos frecuencia. Use 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 resulta en 100 veces menos llamadas de actualización de base de datos. Por supuesto, cada una de esas llamadas toma más tiempo en completarse, pero al final, al final, saldrás adelante. Tu kilometraje puede variar, pero este fue el mundo más rápido para mí.

Y supera la excepción que estabas viendo.

EDITAR Revisé esta pregunta después de ejecutar el Analizador de SQL y actualicé algunas cosas para mejorar el rendimiento. Para cualquiera que esté interesado, aquí hay una muestra de SQL que muestra lo que está creado por la base de datos.

El primer bucle no necesita saltar 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 subsiguientes deben omitir fragmentos anteriores de resultados, por lo que 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


De hecho, no puede guardar los cambios dentro de un bucle 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 haga 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.


Después de mucho arrancarme el cabello, descubrí que los bucles foreach eran los culpables. Lo que debe suceder es llamar a EF pero devolverlo a un IList<T> de ese tipo de destino y luego hacer un bucle en el IList<T> .

Ejemplo:

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


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


En mi caso, el problema apareció cuando llamé a Stored Procedure a través de EF y luego SaveChanges lanzó esta excepción. El problema estaba en llamar al procedimiento, el enumerador no fue eliminado. He arreglado 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 } }


Estaba teniendo este 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 la eliminación del 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 proc almacenado fuera de EF y esto funcionó bien. Por alguna razón, cuando llamo al proceso almacenado utilizando la forma EF de hacer las cosas, deja algo abierto.


Estoy un poco tarde, pero también tuve este error. Resolví el problema comprobando cuáles eran los valores que se 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.

Entonces, en mi situación: compruebe la consulta en busca de errores, depurando el resultado que devuelve la consulta. Después de eso corregir la consulta.

Espero que esto ayude a resolver problemas futuros.


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

El método Invocar SaveChanges () inicia una transacción que revierte automáticamente todos los cambios persistentes en la base de datos si se produce una excepción antes de que se complete la iteración; De lo contrario se compromete la transacción. 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 números masivos de entidades.

Si intenta invocar SaveChanges () antes de que se hayan procesado todos los datos, incurra en una excepción "No se permite la nueva transacción porque hay otros subprocesos que se ejecutan 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 varios Conjuntos de registros activos (MARS) habilitados por la cadena de conexión (la cadena de conexión predeterminada de EF habilita MARS)

A veces es mejor entender por qué suceden las cosas ;-)


Hacer sus listas consultables a .ToList () y debería funcionar bien.


Llego mucho tarde a la fiesta, pero hoy enfrenté el mismo error y mi resolución fue simple. Mi escenario era similar a este código dado que estaba haciendo transacciones DB dentro de bucles anidados para cada uno.

El problema es que una transacción de base de datos única lleva un poco más de tiempo que para cada bucle, por lo que una vez que la transacción anterior no está completa, la nueva tracción lanza una excepción, por lo que la solución es crear un nuevo objeto en el bucle para cada bucle. donde estas haciendo una transaccion db

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

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


Necesitaba leer un ResultSet enorme y actualizar algunos registros en la tabla. Intenté usar trozos como se sugiere en la answer Drew Noakes .

Desafortunadamente, después de 50000 registros tengo OutofMemoryException. El conjunto de datos de gran tamaño de Entity Framework de respuesta , sin excepción de memoria , explica que

EF crea una segunda copia de los datos que se usa para la detección de cambios (para que pueda persistir los cambios en la base de datos). EF mantiene este segundo conjunto durante el tiempo de vida del contexto y es este conjunto que te está quedando sin memoria.

La recomendación es renovar su contexto cada lote.

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

A continuación 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<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 es una estructura simple con propiedades From y To.


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

Y encontré que, este error se puede lanzar cuando un disparador de la tabla de la base de datos recibe un error.

para su información, puede verificar los disparadores de sus tablas también cuando reciba este error.


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

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


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


Utilice siempre su selección como lista

P.ej:

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

Luego recorra la colección mientras guarda los cambios.

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


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

Asegúrese de que, antes de ejecutar los comandos de manipulación de datos, como inserciones y actualizaciones, haya cerrado todos los lectores de SQL activos anteriores.

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

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