【问题标题】:UPDATE issue with .Include() EF Coe.Include() EF Core 的更新问题
【发布时间】:2021-07-04 19:01:37
【问题描述】:

我一直在关注ms docs tutorial 处理 EF 核心中的并发冲突。我有两个模型:

Movie.cs

public class Movie
{
    [Key]
    public int ID { get; set; }

    [Required]
    [StringLength(60, MinimumLength = 3)]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    [DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}", ApplyFormatInEditMode = true)]
    public DateTime ReleaseDate { get; set; }

    [Range(1, 300)]
    [DataType(DataType.Currency)]
    [Column(TypeName = "decimal(18,2)")]
    public decimal Price { get; set; }

    [Required]
    [StringLength(5)]
    [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
    public string Rating { get; set; }

    public string Description { get; set; }

    [Timestamp]
    public byte[] ConcurrencyToken { get; set; }

    public int GenreId { get; set; }
    public Genre Genre { get; set; }

    public override string ToString()
    {
        return Title;
    }
}

Genre.cs

public class Genre
{
    public Genre()
    {
        Movies = new List<Movie>();
    }

    [Key]
    public int GenreId { get; set; }
    
    [Required]
    [StringLength(30, MinimumLength = 5)]
    [Display(Name = "Genre Title")]
    [RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
    public string GenreTitle { get; set; }

    [Timestamp]
    public byte[] ConcurrencyToken { get; set; }

    [Display(Name = "Number of movies")]
    public ICollection<Movie> Movies { get; set; }

    public override string ToString()
    {
        return GenreTitle;
    }
}

当我尝试更新我的模型时,我遇到了一些奇怪的问题,但不知道为什么,没有抛出任何错误,模型只是没有得到更新。这是我的post 更新方法:

public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        // ConcurrencyToken may have changed.
        var movieToUpdate = await _context.Movie
            .Include(m => m.Genre)
            .FirstOrDefaultAsync(m => m.ID == id);

        if(movieToUpdate == null)
        {
            return HandleDeletedMovie();
        }

        // Set ConcurrencyToken to value read in OnGetAsync
        _context.Entry(movieToUpdate).Property(
            m => m.ConcurrencyToken).OriginalValue = Movie.ConcurrencyToken;

        if (await TryUpdateModelAsync<Movie>(
            movieToUpdate,
            "movie",
            m => m.Title, m => m.ReleaseDate,
            m => m.Price, m => m.Rating,
            m => m.GenreId))
        {
            try
            {
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch(DbUpdateConcurrencyException ex)
            {
                var exceptionEntry = ex.Entries.Single();
                var clientValues = (Movie)exceptionEntry.Entity;
                var databaseEntry = exceptionEntry.GetDatabaseValues();

                if(databaseEntry == null)
                {
                    ModelState.AddModelError(string.Empty, "Unable to save. The movie was deleted by another user.");

                    return Page();
                }

                var dbValues = (Movie)databaseEntry.ToObject();
                await SetDbErrorMessage(dbValues, clientValues, _context);

                // Save the current ConcurrencyToken so next postback
                // matches unless an new concurrency issue happens.
                Movie.ConcurrencyToken = (byte[])dbValues.ConcurrencyToken;
                // Clear model error for the next postback
                ModelState.Remove($"{nameof(Movie)}.{nameof(Movie.ConcurrencyToken)}");
            }
        }

        // Get errors from TryUpdate
        var validationErrors = ModelState.Values.Where(E => E.Errors.Count > 0)
        .SelectMany(E => E.Errors)
        .Select(E => E.ErrorMessage)
        .ToList();

        PopelateGenresDropDownList(_context, movieToUpdate.Genre);
        return Page();
    }

更新在TryUpdateModelAsync 线上失败,并且没有转到try-catch。我发现导致问题的错误是对Genre.GenreTitle 属性的验证,该属性是movie 中的Included。即使 ModelState 没有失败,它仍然会导致更新实体的错误。但是,当我检查 Movie.Genre 属性时完全没问题,应该没有任何错误。只有当我从 var movieToUpdate = await _context.Movie.FirstOrDefaultAsync(id) 中删除 .Include 时,它才成功。

现在,我想知道我的错误是什么,因为在文档中嵌套对象实际上是 .Included.

【问题讨论】:

    标签: c# entity-framework-core razor-pages


    【解决方案1】:

    我测试了你的正则表达式^[A-Z]+[a-zA-Z]*$,它似乎不接受空格字符。所以Genre Title 与表达式不匹配并触发错误。

    您可以在[] 中添加空格以告诉表达式您还需要接受空格字符。我不知道它是否应该这样做,但它在RegexStormRegex101 中工作(最后一个没有c#,但我认为正则表达式有点通用)。

    【讨论】:

    • 但是当我尝试更新我的流派实体并且它允许Genre.Title 本身的空间。即使对于只有一个单词的 Titles 也会引发错误......所以我认为可能是别的东西。但是,我会尝试删除我的regex 并重试,谢谢!
    • 顺便说一句,当您在TryUpdateModelAsync 方法中指定字段时,EF 不应该省略其中未指定的字段吗?因为Genre 嵌套在Movie 内部,EF 甚至不应该打扰他,因为没有对其进行任何更改,而且TryUpdate 内部的列列表中也没有提到它。
    • @Miraziz 从未使用过 TruUpdateModelAsync(),但我认为它不能完全避免未指定的属性。由于我们正在讨论模型,因此某处可能存在实例化,因此会读取属性,包括其属性。不确定,但这就是我在这里的看法,如果我错了,也许有人会纠正我
    • 好吧,那么在传递新对象的同时手动指定属性是没有意义的。据我所知,此解决方案用于避免 overposting 问题。
    猜你喜欢
    • 2018-12-24
    • 1970-01-01
    • 1970-01-01
    • 2022-12-09
    • 2021-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多