framework first espaƱol data code c# .net entity-framework entity-framework-4.1 performance-testing

c# - first - Problema de rendimiento de Entity Framework



include entity framework c# (2)

Estoy teniendo un problema de rendimiento interesante con Entity Framework. Estoy usando el código primero.

Aquí está la estructura de mis entidades:

Un libro puede tener muchas reseñas. Una revisión está asociada con un solo libro. Una revisión puede tener uno o varios comentarios. Un comentario está asociado con una revisión.

public class Book { public int BookId { get; set; } // ... public ICollection<Review> Reviews { get; set; } } public class Review { public int ReviewId { get; set; } public int BookId { get; set; } public Book Book { get; set; } public ICollection<Comment> Comments { get; set; } } public class Comment { public int CommentId { get; set; } public int ReviewId { get; set; } public Review Review { get; set; } }

Llené mi base de datos con una gran cantidad de datos y agregué los índices adecuados. Estoy tratando de recuperar un solo libro que tiene 10,000 revisiones usando esta consulta:

var bookAndReviews = db.Books.Where(b => b.BookId == id) .Include(b => b.Reviews) .FirstOrDefault();

Este libro en particular tiene 10.000 comentarios. El rendimiento de esta consulta es de alrededor de 4 segundos. La ejecución de la misma consulta exacta (a través del Analizador de SQL) en realidad devuelve en ningún momento en absoluto. Utilicé la misma consulta y un SqlDataAdapter y objetos personalizados para recuperar los datos y esto sucede en menos de 500 milisegundos.

Usando ANTS Performance Profiler parece que se pasa una gran parte del tiempo haciendo algunas cosas diferentes:

El método Igual se llama 50 millones de veces.

¿Alguien sabe por qué tendría que llamar a esto 50 millones de veces y cómo podría aumentar el rendimiento de esto?


¿Por qué se llama Equals 50M veces?

Suena bastante sospechoso. Tienes 10.000 opiniones y 50.000.000 llamadas a Equals . Supongamos que esto se debe a un mapa de identidad implementado internamente por EF. El mapa de identidad garantiza que cada entidad con clave única sea rastreada por el contexto solo una vez, por lo que si el contexto ya tiene una instancia con la misma clave que el registro cargado de la base de datos, no se materializará una nueva instancia y en su lugar utiliza la existente. Ahora, ¿cómo esto puede coincidir con esos números? Mi conjetura aterradora:

============================================= 1st record read | 0 comparisons 2nd record read | 1 comparison 3rd record read | 2 comparisons ... 10.000th record read | 9.999 comparisons

Eso significa que cada nuevo registro se compara con cada registro existente en el mapa de identidad. Al aplicar las matemáticas para calcular la suma de todas las comparaciones, podemos usar algo llamado "secuencia aritmética":

a(n) = a(n-1) + 1 Sum(n) = (n / 2) * (a(1) + a(n)) Sum(10.000) = 5.000 * (0 + 9.999) => 5.000 * 10.000 = 50.000.000

Espero no haber cometido errores en mis suposiciones o cálculos. ¡Espere! Espero haber cometido un error porque esto no parece bueno.

Intente desactivar el seguimiento de cambios = es de esperar que desactive la verificación del mapa de identidad.

Puede ser complicado. Empezar con:

var bookAndReviews = db.Books.Where(b => b.BookId == id) .Include(b => b.Reviews) .AsNoTracking() .FirstOrDefault();

Pero existe una gran posibilidad de que su propiedad de navegación no se complete (porque se maneja mediante el seguimiento de cambios). En tal caso, utilice este enfoque:

var book = db.Books.Where(b => b.BookId == id).AsNoTracking().FirstOrDefault(); book.Reviews = db.Reviews.Where(r => r.BookId == id).AsNoTracking().ToList();

De todos modos, ¿puedes ver qué tipo de objeto se pasa a Igual? Creo que debería comparar solo las claves primarias e incluso 50M las comparaciones de enteros no deberían ser un problema.

Como nota al margen, EF es lento, es un hecho bien conocido. También utiliza la reflexión interna al materializar entidades, por lo que simplemente 10.000 registros pueden tardar "algún tiempo". A menos que ya haya hecho eso, también puede desactivar la creación dinámica de proxy ( db.Configuration.ProxyCreationEnabled ).


Sé que esto suena cojo, pero has intentado al revés, por ejemplo:

var reviewsAndBooks = db.Reviews.Where(r => r.Book.BookId == id) .Include(r => r.Book);

A veces he notado un mejor rendimiento de EF cuando aborda sus consultas de esta manera (pero no he tenido tiempo de averiguar por qué).