SqlException de Entity Framework: no se permite una nueva transacción porque hay otros subprocesos ejecutándose en la sesión

601

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í)?

Keith Barrows
fuente
66
Esta es la pregunta más detallada que he visto.
99
¿Alguien pierde procedimientos almacenados todavía?
David

Respuestas:

690

Después de mucho arrancarme el cabello descubrí que los foreachbucles eran los culpables. Lo que debe suceder es llamar a EF pero devolverlo a uno IList<T>de ese tipo de destino y luego hacer un bucle en elIList<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;
    // ...
}
Keith Barrows
fuente
14
Sí, esto también me causó dolor de cabeza. ¡Casi me caigo de la silla cuando encontré el problema! Entiendo las razones técnicas detrás del problema, pero esto no es intuitivo y no ayuda al desarrollador a caer en el "pozo del éxito" blogs.msdn.com/brada/archive/2003/10/02/50420. aspx
Doctor Jones
99
¿No es malo para el rendimiento de grandes conjuntos de datos? Si tiene millones de registros en la tabla. ToList () los absorberá a todos en la memoria. Me encuentro con este mismo problema y me preguntaba si lo siguiente sería factible a) Separar la entidad b) Crear un nuevo ObjectContext y adjuntarle la entidad separada. c) Llame a SaveChanges () en el nuevo ObjectContext d) Separe la entidad del nuevo ObjectContext e) Vuelva a conectarlo al viejo ObjectContext
Abhijeet Patel
150
El problema es que no puede llamar SaveChangesmientras 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.
Drew Noakes
44
Habiendo sido mordido también, agregué esto a Microsoft Connect: connect.microsoft.com/VisualStudio/feedback/details/612369/… No dude en votarlo.
Ian Mercer
36
Nuestros desarrolladores tienden a agregar .ToList () a cualquier consulta LINQ sin pensar en las consecuencias. ¡Esta debe ser la primera vez que agrega .ToList () es realmente útil!
Marc
267

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.

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

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

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

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 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
Drew Noakes
fuente
17
Gracias. Su explicación fue mucho más útil que la marcada como "Respondida".
Wagner da Silva
1
Esto es genial. solo una cosa: si está consultando en una columna y actualizando el valor de esa columna, debe tener cuidado con chunkNumber ++; . Supongamos que tiene una columna "ModifiedDate" y está consultando .Where (x => x.ModifiedDate! = Null), y al final de foreach establece un valor para ModifiedDate. De esta manera, no está iterando la mitad de los registros porque se omite la mitad de los registros.
Arvand
Desafortunadamente, en grandes conjuntos de datos obtendrá la excepción OutofMemoryException; vea la explicación en el conjunto de datos grandes de Entity Framework, sin excepción de memoria . Describí cómo renovar su contexto cada lote en SqlException desde Entity Framework: no se permite una nueva transacción porque hay otros subprocesos ejecutándose en la sesión
Michael Freidgeim
Creo que esto debería funcionar. salto de var = 0; const int take = 100; Enumerar emps <Employee>; while ((emps = db.Employees.Skip (skip) .Take (take) .ToList ()). Count> 0) {skip + = take; foreach (var emp in emps) {// Hacer cosas aquí}} Yo formularía esta respuesta, pero estaría enterrada debajo de las pilas de respuestas a continuación y se relaciona con esta pregunta.
jwize
124

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:

// 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();
    }
} 
Mark Stafford - MSFT
fuente
66
Si toma la ruta de la transacción, simplemente arrojar un TransactionScope podría no solucionarlo; no olvide extender el tiempo de espera si lo que está haciendo podría llevar mucho tiempo, por ejemplo, si va a depurar de forma interactiva el código que hace el Llamada DB. Aquí está el código que extiende el tiempo de espera de la transacción a una hora: usando (var transacción = new TransactionScope (TransactionScopeOption.Required, new TimeSpan (1, 0, 0)))
Chris Moschini
¡Me topé con este error la primera vez que me desvié de la "ruta del tutorial" en un ejemplo real por mi cuenta! Para mí, sin embargo, la solución más simple, AHORRE DESPUÉS DE LA ITERACIÓN, ¡mejor! (Creo que el 99% de las veces este es el caso, y solo el 1% realmente DEBE realizar una base de datos salvo DENTRO del ciclo)
Spiderman
Bruto. Acabo de encontrarme con este error. Muy asqueroso. La segunda sugerencia funcionó de maravilla para mí junto con mover mis SaveChanges al bucle. Pensé que guardar los cambios fuera del ciclo era mejor para los cambios por lotes. Pero esta bien. ¡¿Supongo que no?! :(
Sr. Young
No funcionó para mí .NET 4.5. Cuando utilicé el TransactionScope recibí el siguiente error "El proveedor subyacente falló en EnlistTransaction. {" El administrador de transacciones asociado ha deshabilitado su soporte para transacciones remotas / de red. (Excepción de HRESULT: 0x8004D025) "}". Termino haciendo el trabajo fuera de la iteración.
Diganta Kumar
Usar TransactionScope es peligroso, porque la tabla está bloqueada durante el tiempo de toda la transacción.
Michael Freidgeim
19

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

Edgardo Pichardo C.
fuente
2
Pensé que era interesante ver el "sistema de base de datos regular (RDMS)" aquí
Dinerdo
1
Esto parece incorrecto, ya que llamar repetidamente SaveChanges está bien en el 90% de los contextos en EF.
Pxtl
Parece que llamar repetidamente SaveChanges está bien, a menos que el bucle foreach esté iterando sobre una entidad db.
kerbasaurus
1
¡Ajá! ¡Trae contexto dentro de cada ciclo! (pffft ... ¿qué estaba pensando? ..) ¡Gracias!
Adam Cox
18

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

Majid
fuente
Esta es la mejor opción que descubrí en mi caso debido a guardar dentro de foreach
Almeida
2
Esto no siempre es una opción.
Pxtl
9

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

             }
mzonerz
fuente
1
Esta no es una buena práctica en absoluto. No debe ejecutar SaveChanges tan a menudo si no lo necesita, y definitivamente no debe "Usar siempre su selección como Lista"
Dinerdo
@Dinerdo realmente depende del escenario. En mi caso, tengo 2 bucles foreach. El externo tenía la consulta db como la lista. Por ejemplo, este foreach atraviesa dispositivos de hardware. Foreach interno recupera varios datos de cada dispositivo. Según el requisito, necesito guardar en la base de datos los datos después de que se recuperen de cada dispositivo uno por uno. No es una opción guardar todos los datos al final del proceso. Encontré el mismo error pero la solución de mzonerz funcionó.
jstuardo
@jstuardo ¿Incluso con lotes?
Dinerdo
@Dinerdo Estoy de acuerdo en que no es una buena práctica a nivel filosófico. Sin embargo, existen varias situaciones en las que dentro del bucle for el código llama a otro método (digamos un método AddToLog ()) que incluye una llamada a db.SaveChanges () localmente. En esta situación, realmente no puede controlar la llamada a db. Guardar cambios. En este caso, usar un ToList () o una estructura similar funcionará como lo sugiere mzonerz. ¡Gracias!
A. Varma
En la práctica, esto te lastimará más de lo que ayudará. Mantengo lo que dije: ToList () definitivamente no debe usarse todo el tiempo, y guardar los cambios después de cada elemento es algo que debe evitarse siempre que sea posible en una aplicación de alto rendimiento. Esta sería una solución temporal IMO. Cualquier método de registro que tenga también debería aprovechar idealmente el almacenamiento en búfer.
Dinerdo
8

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

Herman Van Der Blom
fuente
1
Una buena manera de evitar esto es cuando tiene un lector abierto para abrir un segundo y poner esas operaciones en el segundo lector. Esto es algo que puede necesitar cuando actualiza master / details en el marco de la entidad. Abre la primera conexión para el registro maestro y la segunda para los registros detallados. si solo estás leyendo no debería haber problemas. Los problemas ocurren durante la actualización.
Herman Van Der Blom
Explicación útil tienes razón, es bueno entender por qué están sucediendo cosas.
Dov Miller
8

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

Wojciech Seweryn
fuente
1
Proporcione un ejemplo en lugar de solo publicar una solución.
Ronnie Oosting
5

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.

MikeKulls
fuente
3
Tuve un problema similar recientemente: la razón en mi caso fue la SELECTdeclaración en el procedimiento almacenado que produjo un conjunto de resultados vacío y si ese conjunto de resultados no se leyó, SaveChangesarrojó esa excepción.
°
Lo mismo con el resultado no leído de SP, muchas gracias por la pista)
Pavel K
4

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:

//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 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:

//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
fuente
Esta es una gran alternativa que pensé e hice, pero esto necesita ser votado. Nota: i) puede iterar como enumerable, lo cual es bueno para conjuntos muy grandes; ii) Puede usar el comando NoTracking para evitar problemas al cargar tantos registros (si ese es su escenario); iii) También me gusta la opción de solo clave principal: es muy inteligente porque está cargando muchos menos datos en la memoria, pero no está tratando con Take / Skip en un conjunto de datos subyacente potencialmente dinámico.
Todd
4

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

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

Así que en el proyecto tuve exactamente el mismo problema, el problema no estaba en foreachel.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:

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();
VeldMuijz
fuente
¿Podría explicar cuál cree que era el problema? resolviste esto creando un nuevo Dbcontext cada vez?
eran otzap
2

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.

nadir
fuente
2

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

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

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:

  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.

Michael Freidgeim
fuente
No pude ver cómo estabas "renovando" tu contexto. Parece que simplemente estás creando un nuevo contexto para cada fragmento.
Suncat2000
@ Suncat2000, tiene razón, el contexto debe ser un objeto de corta duración stackoverflow.com/questions/43474112/…
Michael Freidgeim
2

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.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

La EnsureTransactionsForFunctionsAndCommandsopción permite que el SP se ejecute sin crear su propia transacción y el error ya no se genera.

Propiedad DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands

Mermelada
fuente
1

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.

Vinod T. Patil
fuente
77
¿Qué significa "cerrar un lector" en Entity Framework? No hay un lector visible en una consulta como var result = from customer en myDb.Customers donde customer.Id == customerId selecciona customer; return result.FirstOrDefault ();
Anthony
@Anthony Como dicen otras respuestas, si usa EF para enumerar sobre una consulta LINQ (IQueryable), el DataReader subyacente permanecerá abierto hasta que se repita la última fila. Pero aunque MARS es una característica importante para habilitar en una cadena de conexión, el problema en el OP todavía no se resuelve con MARS solo. El problema es intentar guardar cambios mientras un DataReader subyacente aún está abierto.
Todd
1

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();
}
usuario2918896
fuente
2
Bienvenido a SO! Considere agregar una explicación y / o enlaces que describan por qué esto funciona para usted. Las respuestas de solo código generalmente no se consideran de buena calidad para SO.
codeMagic
1

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:

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
   }
}
Tomás Kubes
fuente
0

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í:

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 ### //
                    }
                }
Usman
fuente
0

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.

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

Espero que esto ayude a resolver problemas futuros.

Max
fuente