.net multithreading frameworks entity

.net - Entity Framework y multi threading



multithreading frameworks (5)

Estamos teniendo algunos problemas para diseñar nuestra aplicación impulsada por el marco de múltiples subprocesos de la Entidad y nos gustaría recibir alguna orientación. Estamos creando entidades en diferentes subprocesos, las entidades se agregan a colecciones que luego están enlazadas a varios controles wpf. La clase ObjectContext no es segura para subprocesos, por lo que al gestionar esto tenemos básicamente 2 soluciones:

La solución 1 tiene un contexto único y usa cuidadosamente el bloqueo para garantizar que no haya 2 subprocesos que accedan al mismo tiempo. Esto sería relativamente simple de implementar, pero requeriría que el contexto esté vivo durante la aplicación. ¿Es una mala idea tener una única instancia de contexto abierta como esta?

La solución 2 consiste en crear objetos de contexto bajo demanda y luego separar los objetos inmediatamente, luego mantenerlos en nuestras propias colecciones y luego volver a adjuntarlos para realizar cualquier actualización. Sin embargo, esto tiene algunos problemas graves de uso, ya que cuando los objetos se desprenden pierden referencias a los objetos de propiedad de navegación. También existe el problema de que 2 subprocesos aún podrían intentar acceder a un único objeto, y ambos intentan adjuntarlo () a un contexto. Además, necesitaríamos proporcionar un nuevo contexto cada vez que quisiéramos acceder a las propiedades de navegación de una entidad.

P: ¿Son válidas alguna de las dos soluciones? Si no, ¿cómo recomiendas que hagamos frente a esto?


Acabo de tener un proyecto donde tratar de usar EF con multi threading causó errores.

Lo intenté

using (var context = new entFLP(entity_connection)) { context.Product.Add(entity); context.SaveChanges(); return entity; }

pero simplemente cambió el tipo de error del error del lector de datos al error de múltiples hilos.

La solución simple era usar procedimientos almacenados con las importaciones de la función EF

using (var context = new entFLP(entity_connection)) { context.fi_ProductAdd(params etc); }

La clave es ir a la fuente de los datos y evitar el modelo de datos.


En primer lugar, supongo que ha leído el artículo "Multithreading y Entity Framework" en MSDN.

La solución n. ° 1 es casi seguro la más segura desde una perspectiva de enhebrado, ya que está garantizando que solo un hilo interactúa con el contexto en cualquier momento dado. No hay nada intrínsecamente incorrecto en mantener el contexto, no está manteniendo las conexiones de la base de datos abiertas detrás de escena, por lo que es solo una sobrecarga de memoria. Esto podría, por supuesto, presentar un problema de rendimiento si terminas con un cuello de botella en ese hilo y toda la aplicación está escrita con suposiciones de subprocesos de subprocesos únicos.

La solución n. ° 2 me parece inviable: terminaría con errores sutiles en toda la aplicación donde las personas olvidan volver a adjuntar (o separar) entidades.

Una solución es no usar los objetos de su entidad en la capa UI de la aplicación. Lo recomendaría de todos modos, lo más probable es que la estructura / disposición de los objetos de la entidad no sea óptima para la forma en que desea mostrar las cosas en su interfaz de usuario (esta es la razón para la familia de patrones MVC ). Su DAL debe tener métodos que sean específicos de la lógica empresarial ( UpdateCustomer por ejemplo), y debe decidir internamente si crear un nuevo contexto o usar uno almacenado. Puede comenzar con el enfoque de un solo contexto almacenado, y luego, si se encuentra con problemas de cuello de botella, tiene un área de superficie limitada donde necesita hacer cambios.

La desventaja es que necesitas escribir mucho más código, tendrías tus entidades EF, pero también tendrías entidades comerciales que tienen propiedades duplicadas y una cardinalidad potencialmente diferente de muchas de las entidades EF. Para mitigar esto, puede utilizar marcos como AutoMapper para simplificar las propiedades de copia de las entidades EF a las entidades comerciales y viceversa.


Ha pasado algún tiempo desde que se formuló la pregunta, pero recientemente me encontré con un tipo de problema similar y terminé haciendo lo siguiente que nos ayudó a cumplir los criterios de rendimiento.

Básicamente, divides tu lista en fragmentos y los procesas dentro de sperate threads de una manera multihilo. Cada nuevo hilo también inicia su propio flujo que requiere que sus entidades estén unidas.

Una cosa a tener en cuenta es que su base de datos debe estar habilitada para el aislamiento de instantáneas; de lo contrario, podrías terminar con puntos muertos. Debería decidir si esto está bien para la operación que está realizando y el proceso comercial relacionado. En nuestro caso, se trata de una simple actualización de la entidad del producto.

Probablemente necesites hacer algunas pruebas para decidir el mejor tamaño de fragmento y también limitar el paralelismo, por lo que siempre hay recursos para completar la operación.

private void PersistProductChangesInParallel(List<Product> products, Action<Product, string> productOperationFunc, string updatedBy) { var productsInChunks = products.ChunkBy(20); Parallel.ForEach( productsInChunks, new ParallelOptions { MaxDegreeOfParallelism = 20 }, productsChunk => { try { using (var transactionScope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot })) { var dbContext = dbContextFactory.CreatedbContext(); foreach (var Product in productsChunk) { dbContext.products.Attach(Product); productOperationFunc(Product, updatedBy); } dbContext.SaveChanges(); transactionScope.Complete(); } } catch (Exception e) { Log.Error(e); throw new ApplicationException("Some products might not be updated", e); } }); }


He visto una docena de subprocesos de con respecto a EF y multihebra. Todos ellos tienen respuestas que explican el problema en profundidad, pero que en realidad no muestran cómo solucionarlo.

EF no es seguro para subprocesos, todos lo sabemos por ahora. Pero en mi experiencia, el único riesgo son las creaciones / manipulaciones del contexto. En realidad, hay una solución muy simple para esto, donde puedes mantener tu carga lenta.

Digamos que tienes una aplicación WPF y un sitio web MVC. donde la aplicación WPF usa multihreading. Simplemente descarta el contexto db en multihilo y guárdalo cuando no lo estés. Ejemplo de un sitio web de MVC, el contexto se eliminará automáticamente después de que se haya presentado la vista.

En la capa de aplicación de WPF, utilizas esto:

ProductBLL productBLL = new ProductBLL(true);

En la capa de aplicación MVC, utilizas esto:

ProductBLL productBLL = new ProductBLL();

Cómo debería ser la capa lógica de su negocio de producto:

public class ProductBLL : IProductBLL { private ProductDAO productDAO; //Your DB layer public ProductBLL(): this(false) { } public ProductBLL(bool multiThreaded) { productDAO = new ProductDAO(multiThreaded); } public IEnumerable<Product> GetAll() { return productDAO.GetAll(); } public Product GetById(int id) { return productDAO.GetById(id); } public Product Create(Product entity) { return productDAO.Create(entity); } //etc... }

Cómo se vería la capa lógica de su base de datos:

public class ProductDAO : IProductDAO { private YOURDBCONTEXT db = new YOURDBCONTEXT (); private bool _MultiThreaded = false; public ProductDAO(bool multiThreaded) { _MultiThreaded = multiThreaded; } public IEnumerable<Product> GetAll() { if (_MultiThreaded) { using (YOURDBCONTEXT db = new YOURDBCONTEXT ()) { return db.Product.ToList(); //USE .Include() For extra stuff } } else { return db.Product.ToList(); } } public Product GetById(int id) { if (_MultiThreaded) { using (YOURDBCONTEXT db = new YOURDBCONTEXT ()) { return db.Product.SingleOrDefault(x => x.ID == id); //USE .Include() For extra stuff } } else { return db.Product.SingleOrDefault(x => x.ID == id); } } public Product Create(Product entity) { if (_MultiThreaded) { using (YOURDBCONTEXT db = new YOURDBCONTEXT ()) { db.Product.Add(entity); db.SaveChanges(); return entity; } } else { db.Product.Add(entity); db.SaveChanges(); return entity; } } //etc... }


Usted no quiere un contexto duradero. Idealmente deberían ser durante la vida de una operación de solicitud / datos.

Cuando lidiaba con problemas similares, terminé implementando un repositorio que almacenaba en caché las Entidades PK para un tipo dado, y permitía un ''LoadFromDetached'' que Encontraría la entidad en la base de datos, y ''copiaba'' todas las propiedades escalares excepto PK sobre el nuevo entidad adjunta.

El rendimiento tendrá un poco de éxito, pero proporciona una forma a prueba de balas para asegurarse de que las propiedades de navegación no se dañen al "olvidar" sobre ellas.