Por que o Entity Framework não está inicializando minhas coleções de navegação?
-
21-12-2019 - |
Pergunta
Estou criando um sistema de administração de torneios ASP.NET MVC4 que, como classe contendo Tournament
com uma coleção de Round
objetos.Sou novo no EF Code First, mas entendi que o EF é deveria inicializar minha coleção de objetos com classes de proxy observadas na instanciação, desde que eu os tenha declarado como virtuais.Por que eles são nulos quando tento adicionar elementos a eles do controlador ASP.NET no código abaixo?
public class Tournament
{
public int Id { get; set; }
[Required]
public String Name { get; set; }
public virtual ICollection<Contestant> Contestants { get; set; }
public virtual ICollection<Round> Rounds { get; set; }
public void InitializeDefaults()
{
var round = new Round("Round 1");
Rounds.Add(round); // Generates NullReferenceException when called from controller
}
}
public class Round
{
public long Id { get; set; }
public int MaxContestants { get; set; }
public String Title { get; set; }
public Round() { }
public Round(string title) : this()
{
Title = title;
}
}
public class MainController {
// (...)
[HttpPost]
public ActionResult CreateTournament(Tournament tournament)
{
var db = new DataContext();
var dbTournament = db.Tournaments.Create();
dbTournament.Name = tournament.Name;
db.Tournaments.Add(dbTournament);
dbTournament.InitializeDefaults();
db.SaveChanges();
return RedirectToRoute(new { action = "Main", tournamentId = dbTournament.Id });
}
}
public class DataContext : DbContext
{
public IDbSet<Tournament> Tournaments { get; set; }
public IDbSet<Judge> Judges { get; set; }
public IDbSet<Contestant> Contestants { get; set; }
}
atualizar
Reinicializar o dataContext após salvar a entidade resolve meu problema.Mas não da maneira certa.A questão original permanece.
Alterado CreateTournament
-método
[HttpPost]
public ActionResult CreateTournament(Tournament tournament)
{
var db = App.ServiceLocator.GetDataBase();
db.Tournaments.Add(tournament);
db.SaveChanges();
db.Dispose();
// Reinitializing the datacontext
db = App.ServiceLocator.GetDataBase();
var dbTournament = db.GetTournament(tournament.Id);
dbTournament.InitializeDefaults();
db.SaveChanges();
return RedirectToRoute(new { action = "Main", tournamentId = dbTournament.Id });
}
Solução
Só funcionaria como você espera se você fosse anexando o criado dbTournament
ao contexto.Somente nesse caso faz sentido criar a coleção e prepará-la para carregamento lento.Mas você é adicionando o dbTournament
como uma nova entidade e neste caso não pode haver nenhum dependente Round
s no banco de dados que poderia se referir ao novo torneio, portanto, uma consulta de carregamento lento não retornaria um resultado de qualquer maneira e a EF não cria a coleção para carregamento lento em primeiro lugar.
Você poderia aplicar truques como anexar o dbTournament
primeiro e depois adicionando-o ao contexto.Mas isso só geraria consultas desnecessárias ao banco de dados, acionadas por carregamento lento, sem resultado e é bastante hackeado.Na minha opinião, a solução mais simples e razoável é a inicialização da coleção padrão em um construtor padrão...
public Tournament()
{
Rounds = new HashSet<Round>();
}
...ou pelo menos um guarda em seu InitializeDefaults
método:
public void InitializeDefaults()
{
var round = new Round("Round 1");
if (Rounds == null)
Rounds = new HashSet<Round>();
Rounds.Add(round);
}