ef code first - many - Entity Framework: configuro la clave foránea, SaveChanges luego accedo a la propiedad de navegación, pero no carga la entidad relacionada. Por qué no?
linq entity framework async (4)
Estoy usando esta clase de Entidad con Entity Framework 5 Code First:
public class Survey
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string SurveyName { get; set; }
[Required]
public int ClientID { get; set; }
[ForeignKey("ClientID")]
public virtual Client Client { get; set; }
}
Y en el método Create de mi Controller hago esto:
Survey entity = new Survey()
{
SurveyName = "Test Name",
ClientID = 4
};
db.Surveys.Add(entity);
db.SaveChanges();
Client c1 = entity.Client; //Why is this null?
Client c2 = db.Clients.Find(entity.ClientID); //But this isn''t?
string s2 = c2.ClientName;
string s1 = c1.ClientName; //null reference thrown here
La propiedad de navegación del Cliente permanece nula después de GuardarCambios. Esperaba que la llamada para cargar el cliente de la base de datos porque existe la clave externa. ¿Por qué no hizo eso?
EDITAR El código aquí proviene de cuando mis controladores dependían de DbContext
. No mucho después de que lo obtuve, volví a factorizar el código para usar repositorios y una unidad de trabajo. Parte de ese movimiento fue impulsado por el hecho de que simplemente se sentía mal usar Create
cuando quería usar new
. Lo que sucedió entonces fue que me encontré con un problema sobre cómo garantizar que se creen proxies cuando se utiliza el patrón de repositorio .
Esperaba que la llamada para cargar el cliente de la base de datos porque existe la clave externa. ¿Por qué no hizo eso?
No hizo eso porque no lo has pedido. Después de la llamada a SaveChanges()
, EF no tiene los datos en la fila referenciada y no hará una llamada de base de datos potencialmente redundante para obtenerla.
Llamar a db.Clients.Find(...
le dice a EF que vaya a buscar la fila desde la base de datos, y por eso devuelve el objeto.
Como dijo @NicholasButler, al llamar a SaveChanges
hace lo que dice en la lata: puede ver esto si depura su código: la salida de Intellitrace mostrará el SQL que ha generado para la inserción / actualización que está persistiendo, pero no habrá ninguna subsiguiente seleccionar.
Tenga en cuenta que a menos que esté ansioso por cargar (utilizando el método Include
), las entidades relacionadas no se cargan al realizar una recuperación, por lo que es lógico pensar que la creación / actualización tampoco lo haga.
El marco de la entidad (desde las versiones 4.1 y posteriores) admite la carga diferida. Lo que esto significa es que si está habilitado, codifique como Client c1 = entity.Client;
debería cargar ese objeto de Client
. Para que quede claro, esta operación no está directamente relacionada con la llamada SaveChanges
.
Vale la pena comprobar si db.Configuration.LazyLoadingEnabled
está establecido en true
. De lo contrario, intente configurarlo para que sea true
y vea si el Client c1 = entity.Client;
sigue siendo nulo.
En resumen, llamar a SaveChanges
no desencadena una carga, pero si la carga diferida está habilitada, el acceso a entity.Client
debe desencadenar una carga de la entidad si aún no se ha cargado.
Editar:
Debería haberlo pensado antes, pero no vas a recibir cargas perezosas en tu objeto de Survey entity
. La razón es que EF trabaja su magia de carga perezosa creando una clase derivada de la suya pero anulando las propiedades marcadas como virtual
para admitir la carga diferida. Hace esto cuando lleva a cabo una recuperación, por lo que su objeto entity
no cargará nada perezoso tal como está.
Pruebe esto justo después de su llamada a SaveChanges
:
Survey entity2 = db.Surveys.Find(entity.ID);
Client c1 = entity2.Client;
Esto debería exhibir el comportamiento que buscas.
Debe definir todas las propiedades de la clase Survey
como virtuales para habilitar la carga diferida.
Consulte http://msdn.microsoft.com/en-us/library/vstudio/dd468057(v=vs.100).aspx para obtener más información.
Para garantizar que la carga diferida de una propiedad de navegación funcione después de haber creado la matriz, no debe crear la Survey
con el new
operador, sino crearla mediante la instancia de contexto porque creará una instancia de un proxy dinámico que es capaz de cargar la carga de forma lenta el Client
relacionado. Para eso es el DbSet<T>.Create()
:
Survey entity = db.Surveys.Create();
entity.SurveyName = "Test Name";
entity.ClientID = 4;
db.Surveys.Add(entity);
db.SaveChanges();
Client c1 = entity.Client;
string s1 = c1.ClientName;
// will work now if a Client with ID 4 exists in the DB
Solo para enfatizar: no es la entity.ClientID = 4;
línea. entity.ClientID = 4;
o db.Surveys.Add(entity);
o db.SaveChanges
que carga el cliente desde el DB, pero la línea Client c1 = entity.Client;
(carga lenta).