Возвращенный объект не завершен с использованием Entity Framework сразу после входа в Blazor

#c# #entity-framework #blazor #blazor-server-side

#c# #entity-framework #blazor #blazor-на стороне сервера

Вопрос:

проблема описана ниже, сначала я покажу вам код.

В базе данных для моего приложения Blazor есть три таблицы: Factor, FactorEntry, entrySet.

Набор записей содержит несколько FactorEntry, которые принадлежат фактору.

Вот связанные классы модели:

 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; } 
}
 

Это метод, который я использую для запроса набора записей для определенной даты и пользователя:

     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;
    }
 

В моем Index.razor есть указатель даты. Когда дата изменяется, я вызываю метод GetEntrySet для выбранной даты и пользователя, который вошел в систему. При инициализации я меняю дату для выбора даты на текущую дату.

 <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();
        }
    }
}
 

Основная проблема

Все работает нормально. Я меняю дату с помощью datepicker, и GetEntrySet возвращает мне правильный набор записей, включая все FactorEntry с соответствующим фактором.

Но есть один случай, который вызывает проблему: сразу после входа в систему (с использованием ASP.net Core Identity), когда дата изменяется в OnInitialized. GetEntrySet возвращает правильный entrySet с правильным списком FactorEntry, но фактор внутри FactorEntry равен Null.

Я понятия не имею, почему это происходит. Какие-либо подсказки?

Побочный вопрос

Как вы можете видеть в коде, я вызываю асинхронный метод DateChange() в наборе{} ChoosenDate, который привязан к datepicker . В Set{} я не могу его дождаться. Есть идеи, как я мог бы это изменить?

Приветствую, Стефан

Ответ №1:

Единственное, что бросается в глаза, это то, что, пока вы загружаете FactorEntries, вы не хотите загружать FactorEntries под ними:

     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();
 

Я рекомендую избегать двунаправленных ссылок, насколько это возможно, если вы хотите передавать сущности между клиентом и сервером. Будучи сериализованными, они могут добраться до глубины, где ссылки будут просто оставлены #null . Я предполагаю, что вы, вероятно, отключили отложенную загрузку, чтобы избежать отключения сериализаторов при чтении. Вы можете столкнуться с кажущимися прерывистыми или ситуационными проблемами, подобными этому, только из-за того, является ли DbContext отслеживающим фактором или нет в момент, когда вы читаете записи. При отключенной отложенной загрузке, когда вы загружаете набор записей с его коллекцией FactorEntries, поскольку он заполняет каждый FactorEntry, он все равно будет проходить через свой кеш, чтобы увидеть, отслеживается ли какой-либо из упомянутых FactorID, и он будет заполнять эти ссылки, если они доступны. Они будут оставлены #null, если контекст их не отслеживает, а отложенная загрузка отключена.

С Blazor я подозреваю, что DbContext несколько долговечен между обновлениями просмотра (надеюсь, не разделяется между сеансами), поэтому обычно есть большая вероятность, что факторы были бы загружены при нормальных условиях, когда вы бы удосужились запросить записи, но при первом входе в систему факторы для записей этого пользователя скорее всего, его еще не читали. Если это так, я бы был очень осторожен с производительностью в зависимости от того, как долго экземпляр DbContext поддерживается в рабочем состоянии. Чем больше объектов отслеживает DbContext, тем медленнее будут выполняться операции чтения и записи.

Комментарии:

1. The . ThenInclude(fe => fe.Factor) решил мою проблему — спасибо. Мне нужно будет еще несколько раз прочитать и подумать о других вещах, которые вы написали, — спасибо и за это.