I have a question regarding navigation properties in EF and it's initialization. I created a small case to clarify the issue I'm dealing with. Sorry for the extreme long post but I like to be clear .
Under my InMemoryRepository class I created 2 lists, being ‘Tickets’ and ‘TicketResponses’. The static method ‘Seed’ fills these 2 lists. You will notice that I didn’t initialize the ‘Responses’ collection of the object ‘t1’. Further you can find a find method ‘ReadTicket’ that returns a ticket based on it’s ID and a method ‘ReadTicketRepsonse’ that returns a ticket response also based on it’s ID.
Under my TicketManager class, I have an instance of my InMemoryRepository class and I also defined two methods, being ‘GetTicket’ and ‘GetTicketResponse’. Both methods are using the instance of the InMemoryRepository to return a ticket or ticket response based on an ID.
Under my Program class, I created an instance of my TicketManager class. I’m using this instance to get a ticket with ID = 1. From this ticket I’m asking the name and I also do a count on the ‘Responses’ collection from this ticket. The latter will give an System.NullReferenceException because I didn’t initialize the ‘Responses’ collection during the creation of this ticket under my ‘Seed’ method.
Now I will do the same but instead of using in memory lists, I will make use of Entity Framework.
I created a DbContext (EfTestPartFourDbContext) where I have 2 DbSets (Tickets and TicketResponses). Further I have a EfTestPartFoutDbInitializer class that contains a ‘Seed’ method and the EfTestPartFourDbContext class uses to fill it’s DbSets (just for testing).
I created a Repository class that’s completely the same as the InMemoryRepository with the exception it has no longer the 2 lists (Tickets and TicketResponses) but a EfTestPartFourDbContext object to use within the ‘ReadTicket’ and ‘ReadTicketResponse’ methods. Also there is no longer a ‘Seed’ method because the seeding is now done by the EfTestPartFourDbInitializer class.
The TicketManager class does now no longer uses an instance of the InMemoryRepository, but is using an instance of the Repository class.
Also My Program class is unchanged. When u run it, I no longer get the System.NullReferenceException when I do a count on the ‘Responses’ collection from the ticket.
So now my question: Why does this not throwing the NullReferenceExcption? I just as well didn’t initialize the ‘Responses’ collection of the ticket during seeding. Is it that EF somehow initializes it's navigation properties?
Thank yu very much!
Under my InMemoryRepository class I created 2 lists, being ‘Tickets’ and ‘TicketResponses’. The static method ‘Seed’ fills these 2 lists. You will notice that I didn’t initialize the ‘Responses’ collection of the object ‘t1’. Further you can find a find method ‘ReadTicket’ that returns a ticket based on it’s ID and a method ‘ReadTicketRepsonse’ that returns a ticket response also based on it’s ID.
InMemoryRepository:
public class InMemoryRepository : IRepository
{
private static readonly List<Ticket> Tickets = new List<Ticket>();
private static readonly List<TicketResponse> TicketResponses = new List<TicketResponse>();
static InMemoryRepository()
{
Seed();
}
public Ticket ReadTicket(int tickedId)
{
return Tickets.Find(t => t.TicketId == tickedId);
}
public TicketResponse ReadTicketResponse(int ticketResponseId)
{
return TicketResponses.Find(tr => tr.TicketResponseId == ticketResponseId);
}
private static void Seed()
{
Ticket t1 = new Ticket()
{
TicketId = 1,
Name = "Ticket 1"
};
Tickets.Add(t1);
TicketResponse tr1 = new TicketResponse()
{
TicketResponseId = 1,
Name = "Ticket response 1",
Ticket = t1
};
TicketResponses.Add(tr1);
}
}
Under my TicketManager class, I have an instance of my InMemoryRepository class and I also defined two methods, being ‘GetTicket’ and ‘GetTicketResponse’. Both methods are using the instance of the InMemoryRepository to return a ticket or ticket response based on an ID.
TicketManager:
public class TicketManager : ITicketManager
{
private readonly IRepository _repository = new InMemoryRepository();
public Ticket GetTicket(int ticketId)
{
return _repository.ReadTicket(ticketId);
}
public TicketResponse GetTicketResponse(int ticketResponseId)
{
return _repository.ReadTicketResponse(ticketResponseId);
}
}
Under my Program class, I created an instance of my TicketManager class. I’m using this instance to get a ticket with ID = 1. From this ticket I’m asking the name and I also do a count on the ‘Responses’ collection from this ticket. The latter will give an System.NullReferenceException because I didn’t initialize the ‘Responses’ collection during the creation of this ticket under my ‘Seed’ method.
Program:
class Program
{
private static readonly ITicketManager TicketManager = new TicketManager();
static void Main(string[] args)
{
Ticket ticket = TicketManager.GetTicket(1);
Console.WriteLine(ticket.Name);
Console.WriteLine(ticket.Responses.Count);
}
}
Now I will do the same but instead of using in memory lists, I will make use of Entity Framework.
I created a DbContext (EfTestPartFourDbContext) where I have 2 DbSets (Tickets and TicketResponses). Further I have a EfTestPartFoutDbInitializer class that contains a ‘Seed’ method and the EfTestPartFourDbContext class uses to fill it’s DbSets (just for testing).
EfTestPartFourDbContext:
internal class EfTestPartFourDbContext : DbContext
{
public DbSet<Ticket> Tickets { get; set; }
public DbSet<TicketResponse> TicketResponses { get; set; }
public EfTestPartFourDbContext()
{
EfTestPartFourDbInitializer.Initialize(this, true);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlite("Data Source = EfTestPartFour.db")
.UseLoggerFactory(LoggerFactory.Create(b => b.AddDebug()));
}
}
EfTestPartFourDbInitializer:
internal static class EfTestPartFourDbInitializer
{
public static void Initialize(EfTestPartFourDbContext context, bool dropCreateDatabase)
{
if (dropCreateDatabase)
{
context.Database.EnsureDeleted();
}
if (context.Database.EnsureCreated())
{
Seed(context);
}
}
private static void Seed(EfTestPartFourDbContext context)
{
Ticket t1 = new Ticket()
{
TicketId = 1,
Name = "Ticket 1"
};
context.Tickets.Add(t1);
TicketResponse tr1 = new TicketResponse()
{
TicketResponseId = 1,
Name = "Ticket response 1",
Ticket = t1
};
context.TicketResponses.Add(tr1);
context.SaveChanges();
foreach (var entry in context.ChangeTracker.Entries())
{
entry.State = EntityState.Detached;
}
}
}
I created a Repository class that’s completely the same as the InMemoryRepository with the exception it has no longer the 2 lists (Tickets and TicketResponses) but a EfTestPartFourDbContext object to use within the ‘ReadTicket’ and ‘ReadTicketResponse’ methods. Also there is no longer a ‘Seed’ method because the seeding is now done by the EfTestPartFourDbInitializer class.
Repository:
public class Repository : IRepository
{
private readonly EfTestPartFourDbContext _context = new EfTestPartFourDbContext();
public Ticket ReadTicket(int tickedId)
{
return _context.Tickets.Include(t=>t.Responses).FirstOrDefault(t=>t.TicketId == tickedId);
}
public IEnumerable<TicketResponse> ReadResponsesOfTicket(int tickedId)
{
return _context.TicketResponses.Where(tr => tr.Ticket.TicketId == tickedId);
}
public TicketResponse ReadTicketResponse(int ticketResponseId)
{
return _context.TicketResponses.Find(ticketResponseId);
}
public IEnumerable<TicketResponse> ReadTicketResponses()
{
return _context.TicketResponses;
}
}
The TicketManager class does now no longer uses an instance of the InMemoryRepository, but is using an instance of the Repository class.
TicketManager:
public class TicketManager : ITicketManager
{
private readonly IRepository _repository = new Repository();
public Ticket GetTicket(int ticketId)
{
return _repository.ReadTicket(ticketId);
}
public TicketResponse GetTicketResponse(int ticketResponseId)
{
return _repository.ReadTicketResponse(ticketResponseId);
}
}
Also My Program class is unchanged. When u run it, I no longer get the System.NullReferenceException when I do a count on the ‘Responses’ collection from the ticket.
So now my question: Why does this not throwing the NullReferenceExcption? I just as well didn’t initialize the ‘Responses’ collection of the ticket during seeding. Is it that EF somehow initializes it's navigation properties?
Thank yu very much!