1
votes

problem is described below, first I show you the code.

In the database for my Blazor App there are three tables: Factor, FactorEntry, EntrySet.

An EntrySet contains multiple FactorEntry that belong to a Factor.

Here are the associated model classes:

public class Factor
{
    public int FactorId { get; set; }
    public string FactorName { get; set; }
...
    public virtual ICollection<FactorEntry> FactorEntry { get; set; }
}


public class FactorEntry
{
    public int FactorEntryId { get; set; }
    public int EntryValue { get; set; }
    public int FactorId { get; set; }
    public virtual Factor Factor { get; set; }
    public virtual EntrySet EntrySet { get; set; }
}

public class EntrySet
{
    public int EntrySetId { get; set; }
    public DateTime EntrySetDate { get; set; }
    public string UserId { get; set; }
    public virtual ICollection<FactorEntry> FactorEntries { get; set; } 
}

This is the method I use to request an EntrySet for a specific Date & User:

    public async Task<EntrySet> GetEntrySet(DateTime date, string userID)
    {
        var entry = await _context.EntrySets
            .Include(entry => entry.FactorEntries)
            .Where(d => d.EntrySetDate.Date == date.Date)
            .Where(u => u.UserId == userID)
            .FirstOrDefaultAsync();

        return entry;
    }

At my Index.razor there is a datepicker. When the date is changed, I call the GetEntrySet method for the choosen date and the user that is logged in. OnInitialized I change the Date for the Datepicker to the current Date.

<InputDate @bind-Value="this.ChoosenDate" />

@code{

public EntrySet CurrentEntrySet { get; set; } = new EntrySet() {

    FactorEntries = new List<FactorEntry>()
};

private bool entryExisted;

private DateTime choosenDate;

public DateTime ChoosenDate
{
    get { return choosenDate; }
    set
    {
        choosenDate = value;
        this.DateChange();
    }
}

public string UserID { get; set; }

protected override void OnInitialized()
{
    base.OnInitialized();

    var principal = httpContextAccessor.HttpContext.User;
    this.UserID = principal.FindFirstValue(ClaimTypes.NameIdentifier);

    ChoosenDate = DateTime.Now;
}

private async Task DateChange()
{
    if (String.IsNullOrEmpty(this.UserID))
    {
        Console.WriteLine("NoUserID");
    }
    else
    {
        var entrySetForDate = await service.GetEntrySet(this.choosenDate, this.UserID);

        if (entrySetForDate == null)
        {
            this.entryExisted = false;
            await this.CreateNewEntrySet();
            StateHasChanged();
        }
        else
        {
            this.entryExisted = true;
            CurrentEntrySet = entrySetForDate;
            StateHasChanged();
        }
    }
}

Main Problem

Everything works fine. I change the date with the datepicker and GetEntrySet returns me the correct EntrySet including all FactorEntry with the associated Factor.

But there is one case that causes a problem: just after the login (using ASP.net Core Identity), when the date is changed in OnInitialized. GetEntrySet returns the correct EntrySet, with the correct List of FactorEntry, but the Factor inside the FactorEntry is Null.

I have no idea, why this happens. Any clues?

Side-Question

As you can see in code, I call the async method DateChange() in the Set{} of ChoosenDate, that is bound to the datepicker. In the Set{} I can't await it. Any idea how I could change that?

Greetings Stefan

1

1 Answers

2
votes

The one thing that jumps out is that while you are eager-loading FactorEntries, you are not eager-loading the Factor beneath them:

    var entry = await _context.EntrySets
        .Include(entry => entry.FactorEntries)
            .ThenInclude(fe => fe.Factor)
        .Where(d => d.EntrySetDate.Date == date.Date)
        .Where(u => u.UserId == userID)
        .FirstOrDefaultAsync();

I recommend avoiding bi-directional references as much as possible if you want to be passing entities around between client and server. Being serialized they can get to a depth where references will be just left #null. I'm assuming you've probably disabled lazy loading to help avoid the serializers from tripping reads. You can get seemingly intermittent or situational issues like this due to just whether the DbContext happens to be tracking Factors or not at the point that you read the Entries. With lazy loading disabled, when you load an Entry set with it's FactorEntries collection, as it populates each FactorEntry it will still go through it's cache to see if any of the referenced FactorId are tracked and it will populate those references if available. They will be left #null if the context is not tracking them and lazy loading is disabled.

With Blazor I suspect the DbContext is somewhat long-lived between view refreshes (hopefully not shared between sessions) so normally there is a good chance that the Factors would have been loaded under normal conditions when you'd get around to requesting the Entries, but on a first login the Factors for that user's Entries likely haven't been read. If this is the case I'd be very cautious with performance depending on how long a DbContext instance is kept alive. The more entities a DbContext tracks, the slower read and write operations will continue to get.