EF 4.1 cargar colecciones secundarias filtradas que no funcionan para muchos a muchos

He estado buscando la aplicación de filtros al cargar de forma explícita entidades relacionadas y no pude lograr que funcione para una relación de muchos a muchos.

Creé un modelo simple: Modelo

Breve descripción:
Un Student puede tomar muchos Courses y un Course puede tener muchos Students .
Un Student puede hacer muchas Presentation , pero solo un Student puede hacer una Presentation .
Entonces, lo que tenemos es una relación de muchos a muchos entre Students y Courses , así como una relación de uno a varios entre el Student y las Presentations .

También agregué un Student , un Course y una Presentation relacionados entre sí.

Aquí está el código que estoy ejecutando:

 class Program { static void Main() { using (var context = new SportsModelContainer()) { context.Configuration.LazyLoadingEnabled = false; context.Configuration.ProxyCreationEnabled = false; Student student = context.Students.Find(1); context. Entry(student). Collection(s => s.Presentations). Query(). Where(p => p.Id == 1). Load(); context. Entry(student). Collection(s => s.Courses). Query(). Where(c => c.Id == 1). Load(); // Trying to run Load without calling Query() first context.Entry(student).Collection(s => s.Courses).Load(); } } } 

Después de cargar las presentaciones, veo que el conteo de Presentations cambió de 0 a 1: Después de cargar presentaciones . Sin embargo, después de hacer lo mismo con los Courses nada cambia: Después de intentar cargar cursos

Así que trato de cargar los cursos sin llamar a Query y funciona como se esperaba: Cursos cargados

(Eliminé la cláusula Where para resaltar aún más el punto; los últimos dos bashs de carga solo difieren por la llamada “Query ()”)

Ahora, la única diferencia que veo es que una relación es de uno a muchos, mientras que la otra es de muchos a muchos. ¿Es esto un error de EF, o me estoy perdiendo algo?

Y, por cierto, revisé las llamadas SQL para los últimos dos bashs de carga de Course , y son 100% idénticos, por lo que parece que es EF lo que no llena la colección.

Podría reproducir exactamente el comportamiento que describes. Lo que obtuve trabajando es esto:

 context.Entry(student) .Collection(s => s.Courses) .Query() .Include(c => c.Students) .Where(c => c.Id == 1) .Load(); 

No sé por qué deberíamos obligarnos también a cargar el otro lado de la relación de muchos a muchos ( Include(...) ) cuando solo queremos cargar una colección. Para mí, se siente realmente como un error a menos que me pierda alguna razón oculta para este requisito que está documentado en alguna parte o no.

Editar

Otro resultado: su consulta original (sin Incluir) …

 context.Entry(student) .Collection(s => s.Courses) .Query() .Where(c => c.Id == 1) .Load(); 

… en realidad carga los cursos en el DbContext como …

 var localCollection = context.Courses.Local; 

… muestra. El curso con Id 1 está en esta colección, lo que significa: cargado en el contexto. Pero no está en la colección secundaria del objeto estudiantil.

Editar 2

Tal vez no es un error.

Primero que nada: estamos usando aquí dos versiones diferentes de Load :

 DbCollectionEntry.Load() 

Intellisense dice:

Carga la colección de entidades de la base de datos. Tenga en cuenta que las entidades que ya existen en el contexto no se sobrescriben con los valores de la base de datos.

Para la otra versión (método de extensión de IQueryable ) …

 DbExtensions.Load(this IQueryable source); 

… Intellisense dice:

Enumera la consulta de modo que para las consultas del servidor, como las de System.Data.Entity.DbSet, System.Data.Objects.ObjectSet, System.Data.Objects.ObjectQuery y otras, los resultados de la consulta se cargarán en el sistema asociado. .Data.Entity.DbContext, System.Data.Objects.ObjectContext u otro caché en el cliente. Esto es equivalente a llamar a ToList y luego descartar la lista sin la sobrecarga de crear realmente la lista.

Por lo tanto, en esta versión no se garantiza que la colección secundaria esté llena , solo que los objetos se cargan en el contexto.

La pregunta continúa: ¿Por qué se llena la colección de Presentations , pero no la colección de Courses ? Y creo que la respuesta es: debido a la relación de intervalo .

El intervalo de relación es una característica de EF que corrige automáticamente las relaciones entre los objetos que están en el contexto o que simplemente se cargan en el contexto. Pero esto no sucede para todos los tipos de relaciones. Sucede solo si la multiplicidad es 0 o 1 en un extremo.

En nuestro ejemplo, significa: cuando cargamos las Presentations en el contexto (mediante nuestra consulta explícita filtrada), EF también carga la clave externa de las entidades de Presentation a la entidad Student – “transparentemente”, lo que significa que no importa si el FK está expuesto como propiedad en el modelo de no. Este FK cargado permite a EF reconocer que las Presentations cargadas pertenecen a la entidad de Student que ya está en el contexto.

Pero este no es el caso para la colección de Courses . Un curso no tiene una clave foránea para la entidad Student . Existe la tabla de unión de muchos a muchos en el medio. Entonces, cuando cargamos los Courses EF no reconoce que esos cursos pertenecen al Student que está en el contexto, y por lo tanto no arregla la colección de navegación en la entidad de Student .

EF realiza esta corrección automática solo para referencias (no colecciones) por motivos de rendimiento:

Para corregir la relación, EF reescribe la consulta de forma transparente para generar información de relación para todas las relaciones que tiene una multiplicidad de 0..1 o 1 en el otro extremo; en otras palabras, propiedades de navegación que son referencia de entidad. Si una entidad tiene una relación con una multiplicidad mayor que 1, EF no devolverá la información de la relación porque podría ser un golpe de rendimiento y, en comparación con traer un solo extranjero junto con el rest del registro. Traer información de relación significa recuperar todas las claves externas que tienen los registros.

Cita de la página 128 de la guía en profundidad de Zeeshan Hirani para EF .

Se basa en EF 4 y ObjectContext pero creo que esto sigue siendo válido en EF 4.1 ya que DbContext es principalmente un contenedor alrededor de ObjectContext.

Lamentablemente, algo bastante complejo a tener en cuenta al usar Load .

Y otro Editar

Entonces, ¿qué podemos hacer cuando queremos cargar explícitamente un lado filtrado de una relación de muchos a muchos? Quizás solo esto:

 student.Courses = context.Entry(student) .Collection(s => s.Courses) .Query() .Where(c => c.Id == 1) .ToList();