I am a little puzzled as to why this code doesn't work. I have a basic one-to-many relationship where I load a parent and include its children. I later I am trying to navigate from the child back to the parent, but the parent is null and I can't figure out why.
Question:
Why can't I query a graph of parent/child objects and navigate backward through them from child to parent? The parent is always null.
Here are the entities.
public class Budget
{
[Key]
public int Id { get; set; }
public ICollection<Expense> Expenses { get; set; }
public ICollection<Income> Incomes { get; set; }
}
public class Expense
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[StringLength(200)]
public string ExpenseName { get; set; }
[Required]
public decimal Cost { get; set; }
[StringLength(800)]
public string Notes { get; set; }
public DateTime? DueDate { get; set; }
public Budget Budget { get; set; }
}
public class Income
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public decimal Amount { get; set; } = 0;
[Required]
[StringLength(200)]
public string Source { get; set; }
[Required]
public DateTime? PayDate { get; set; } = DateTime.Now;
public Budget Budget { get; set; }
}
Here is the repository query.
public async Task<Budget> GetBudget(int id)
{
try
{
return await context.Budget
.Include(e => e.Expenses)
.Include(i => i.Incomes)
.SingleAsync(b => b.Id == id);
}
catch(Exception x)
{
x.ToString();
}
return null;
}
I want to be able to navigate back through the relation from expense to budget to get the Budget.Income collection.
Expected result:
foreach(Expense expense in Budget.Expenses)
{
if (expense.Budget is not null)
{
ICollection<Income> paychecks = expense.Budget.Incomes; // Why is Budget always null?
}
}
I expected that even if I didn't use the ThenInclude(e => e.Budget) that I should still be able to navigate from the child back to the parent {var budget = expense.Budget}. I'm surprised that this isn't working.
I didn't include the Income entity here, but my goal is to traverse expense.Budget.Incomes to get the collection of incomes in code where I only have access to the expense instance.
After removing ThenInclude(e => e.Budget) I no longer get an error, but the expense.Budget property is still null.
UPDATE
I believe that I found the root cause of my problem. When I added the property Budget to the Expense class I started getting an error when deserializing the objects coming from the API. The HttpClient was throwing an error due to a cyclical reference.
Because I'm fetching the root Budget entity and it's related Expenses, and the Expense has a reference to the Budget I got the cyclical reference error.
I added this code the the startup Blazor server startup class to fix it. I think this is my problem.
services.AddControllersWithViews().AddJsonOptions(x =>
x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
If I change to ReferenceHandler.Preserve I get a different error.
'The JSON value could not be converted to System.Collections.Generic.ICollection`1[BlazorApp.Data.Models.Expense]. Path: $.expenses | LineNumber: 0 | BytePositionInLine: 34.'
What I don't know and would like to solve is how to make this work so I can get the Budget -> Expenses and have the Expense.Budget property point to it's parent Budget instance. My real issue is probably more related to json serialization and deserialization.