Actualmente recibo este error:
System.Data.SqlClient.SqlException: no se permite una nueva transacción porque hay otros subprocesos ejecutándose 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 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 en nuestro servidor Prod 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 marcados con un círculo rojo en el Modelo # 1 son los campos que uso para "asignar" al Modelo # 2. Ignore los círculos rojos en el Modelo # 2: eso es de otra pregunta que tenía que ahora está respondida.
Nota: todavía necesito poner un cheque isDeleted para poder eliminarlo de DB1 si se ha salido del inventario de nuestro 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 debe ser un tirón completo de inventario. Cada vez que se ejecuta allí después de que nada suceda, a menos que ingrese un nuevo inventario en el feed durante la noche.
Entonces, la gran pregunta: ¿cómo puedo resolver el error de transacción que recibo? ¿Necesito soltar y recrear mi contexto cada vez a través de los bucles (no tiene sentido para mí)?
fuente
Respuestas:
Después de mucho arrancarme el cabello descubrí que los
foreach
bucles eran los culpables. Lo que debe suceder es llamar a EF pero devolverlo a unoIList<T>
de ese tipo de destino y luego hacer un bucle en elIList<T>
.Ejemplo:
fuente
SaveChanges
mientras aún está obteniendo resultados de la base de datos. Por lo tanto, otra solución es guardar los cambios una vez que el ciclo se haya completado.Como ya ha identificado, no puede guardar desde un
foreach
que todavía está dibujando desde la base de datos a través de un lector activo.Llamando
ToList()
oToArray()
está bien para pequeños conjuntos de datos, pero cuando tiene miles de filas, consumirá una gran cantidad de memoria.Es mejor cargar las filas en trozos.
Teniendo en cuenta los métodos de extensión anteriores, puede escribir su consulta de esta manera:
El objeto consultable al que llama este método debe ordenarse. Esto se debe a que Entity Framework solo admite
IQueryable<T>.Skip(int)
consultas ordenadas, lo que tiene sentido cuando considera que varias consultas para diferentes rangos requieren que el pedido 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
SaveChanges()
se llama para cada entidad.Si desea mejorar su rendimiento dramáticamente, debe llamar con
SaveChanges()
menos frecuencia. Use un código como este en su lugar:Esto da como resultado 100 veces menos llamadas de actualización de la base de datos. Por supuesto, cada una de esas llamadas lleva más tiempo en completarse, pero al final aún se adelanta. Su kilometraje puede variar, pero esto fue mucho más rápido para mí.
Y evita la excepción que estabas viendo.
EDITAR Volví a 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 la base de datos.
El primer bucle no necesita omitir nada, por lo que es más simple.
Las llamadas posteriores deben omitir fragmentos de resultados anteriores, por lo que introduce el uso de
row_number
:fuente
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 declarando explícitamente una transacción. Aquí hay tres posibles soluciones:
fuente
De hecho, no puede guardar los cambios dentro de un
foreach
bucle en C # usando Entity Framework.context.SaveChanges()
El método actúa como una confirmación en un sistema de base de datos regular (RDMS).Simplemente haga todos los cambios (qué Entity Framework almacenará en caché) y luego guárdelos todos a la vez llamando
SaveChanges()
al 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.
fuente
Simplemente coloque
context.SaveChanges()
después del final de suforeach
(bucle).fuente
Utilice siempre su selección como Lista
P.ej:
Luego recorra la colección mientras guarda los cambios
fuente
FYI: de un libro y algunas líneas ajustadas porque todavía es válido:
Invocar el método SaveChanges () comienza 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, la transacción se confirma. Es posible que sienta la tentación de aplicar el método después de cada actualización o eliminación de la entidad en lugar de después de que se complete la iteración, especialmente cuando actualiza o elimina un gran número de entidades.
Si intenta invocar SaveChanges () antes de que se hayan procesado todos los datos, incurrirá en la excepción "No se permite una nueva transacción porque hay otros subprocesos ejecutándose en la sesión". La excepción ocurre 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 MARS)
A veces es mejor entender por qué están sucediendo cosas ;-)
fuente
Hacer sus listas consultables a .ToList () y debería funcionar bien.
fuente
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 proceso almacenado fuera de EF y esto funcionó bien. Por alguna razón, cuando llamo al proceso almacenado usando la forma EF de hacer las cosas, deja algo abierto.
fuente
SELECT
declaración en el procedimiento almacenado que produjo un conjunto de resultados vacío y si ese conjunto de resultados no se leyó,SaveChanges
arrojó esa excepción.Aquí hay otras 2 opciones que le permiten invocar SaveChanges () en un para cada bucle.
La primera opción es usar un DBContext para generar sus objetos de lista para iterar, y luego crear un segundo DBContext para activar SaveChanges (). Aquí hay un ejemplo:
La segunda opción es obtener una lista de objetos de la base de datos del DBContext, pero seleccionar solo 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:
fuente
Si obtiene este error debido a foreach y realmente necesita guardar una entidad primero dentro del bucle y usar la identidad generada más adelante en el bucle, como fue 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
fuente
Así que en el proyecto tuve exactamente el mismo problema, el problema no estaba en
foreach
el.toList()
estaba realmente en la configuración de AutoFac que utilizamos. Esto creó algunas situaciones extrañas en las que se arrojó el error anterior, pero también se arrojaron muchos otros errores equivalentes.Esta fue nuestra solución: Cambié esto:
A:
fuente
Sé que es una vieja pregunta, pero hoy me enfrenté a este error.
y descubrí que este error se puede generar cuando un desencadenador de tabla de base de datos obtiene un error.
para su información, también puede verificar los activadores de sus tablas cuando reciba este error.
fuente
Necesitaba leer un enorme ResultSet y actualizar algunos registros en la tabla. Traté de usar trozos como se sugiere en la respuesta de Drew Noakes .
Lamentablemente, después de 50000 registros, tengo OutofMemoryException. El conjunto de datos grandes del marco de Entidad de respuesta , la excepción de memoria insuficiente explica que
La recomendación es volver a crear su contexto para cada lote.
Así que recuperé 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 fragmentos de registros al abrir el contexto para cada fragmento. Después de procesar el fragmento, el contexto se cierra y libera la memoria. Asegura que el uso de memoria no está creciendo.
A continuación hay un fragmento de mi código:
FromToRange es una estructura simple con propiedades From y To.
fuente
Comenzamos a ver este error "No se permite una nueva transacción porque hay otros subprocesos ejecutándose en la sesión" después de migrar de EF5 a EF6.
Google nos trajo aquí, pero no estamos llamando
SaveChanges()
dentro del ciclo. Los errores se generaron al ejecutar un procedimiento almacenado utilizando ObjectContext.ExecuteFunction dentro de una lectura de bucle foreach del DB.Cualquier llamada a ObjectContext.ExecuteFunction envuelve la función en una transacción. Comenzar una transacción mientras ya hay un lector abierto provoca el error.
Es posible deshabilitar el ajuste del SP en una transacción configurando la siguiente opción.
La
EnsureTransactionsForFunctionsAndCommands
opción permite que el SP se ejecute sin crear su propia transacción y el error ya no se genera.Propiedad DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands
fuente
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 disparar comandos de manipulación de datos como inserciones, actualizaciones, haya cerrado todos los lectores 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, volveremos inmediatamente de la función si encontramos el registro y olvidamos cerrar el lector.
fuente
El siguiente código funciona para mí:
fuente
En mi caso, el problema apareció cuando llamé a Procedimiento almacenado a través de EF y luego SaveChanges lanzó esta excepción. El problema estaba en llamar al procedimiento, el enumerador no estaba dispuesto. Arreglé el código de la siguiente manera:
fuente
Llego mucho tarde a la fiesta, pero hoy me enfrenté al mismo error y la forma en que lo resolví fue simple. Mi escenario era similar a este código dado que estaba haciendo transacciones de base de datos 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 ciclo, por lo que una vez que la transacción anterior no se completa, la nueva tracción genera una excepción, por lo que la solución es crear un nuevo objeto en el ciclo para cada donde estás haciendo una transacción de db.
Para los escenarios mencionados anteriormente, la solución será así:
fuente
Llego un poco tarde, pero también tuve este error. Resolví el problema comprobando cuáles eran 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.
Espero que esto ayude a resolver problemas futuros.
fuente