我们有3个模型类:

  • 主机
  • 锦标赛批次
  • TournamentBatchItem

  • 主持人有很多TournamentBatch。
    TournamentBatch具有许多TournamentBatchItem。在TournamentBatch表中将有FK主机。

    我们确实对ApplicationDbContext中的SaveChangesAsync进行了覆盖,以允许进行软删除,如下所示:
    public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
        {
            OnBeforeSaving();
    
            return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
        }
    
        private void OnBeforeSaving()
        {
    
            if (_httpContextAccessor.HttpContext != null)
            {
                var userName = _httpContextAccessor.HttpContext.User.Identity.Name;
                var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
    
    
                // Added
                var added = ChangeTracker.Entries().Where(v => v.State == EntityState.Added && typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
    
                added.ForEach(entry =>
                {
                    ((IBaseEntity)entry.Entity).DateCreated = DateTime.UtcNow;
                    ((IBaseEntity)entry.Entity).CreatedBy = userId;
    
                    ((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
                    ((IBaseEntity)entry.Entity).LastModifiedBy = userId;
                });
    
                // Modified
                var modified = ChangeTracker.Entries().Where(v => v.State == EntityState.Modified &&
                typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
    
                modified.ForEach(entry =>
                {
                    ((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
                    ((IBaseEntity)entry.Entity).LastModifiedBy = userId;
                });
    
                // Deleted
                var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
               typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
    
                // var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted).ToList();
    
                deleted.ForEach(entry =>
                {
                    ((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
                    ((IBaseEntity)entry.Entity).DeletedBy = userId;
                });
    
                foreach (var entry in ChangeTracker.Entries()
                                        .Where(e => e.State == EntityState.Deleted &&
                                        e.Metadata.GetProperties().Any(x => x.Name == "IsDeleted")))
                {
                    switch (entry.State)
                    {
                        case EntityState.Added:
                            entry.CurrentValues["IsDeleted"] = false;
                            break;
    
                        case EntityState.Deleted:
                            entry.State = EntityState.Modified;
                            entry.CurrentValues["IsDeleted"] = true;
                            break;
                    }
                }
            }
            else
            {
                // DbInitializer kicks in
            }
        }
    

    在我们的模型中:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace AthlosifyWebArchery.Models
    {
      public class TournamentBatch : IBaseEntity
      {
        [Key]
        public Guid TournamentBatchID { get; set; }
    
        public Guid HostID { get; set; }
    
        public string Name { get; set; }
    
        public string BatchFilePath { get; set; }
    
        [Display(Name = "Batch File Size (bytes)")]
        [DisplayFormat(DataFormatString = "{0:N1}")]
        public long BatchFileSize { get; set; }
    
        [Display(Name = "Uploaded (UTC)")]
        [DisplayFormat(DataFormatString = "{0:F}")]
        public DateTime DateUploaded { get; set; }
    
        public DateTime DateCreated { get; set; }
    
        public string CreatedBy { get; set; }
    
        public DateTime LastDateModified { get; set; }
    
        public string LastModifiedBy { get; set; }
    
        public DateTime? DateDeleted { get; set; }
    
        public string DeletedBy { get; set; }
    
        public bool IsDeleted { get; set; }
    
        public Host Host { get; set; }
    
        public ICollection<TournamentBatchItem> TournamentBatchItems { get; set; }
    
        [Timestamp]
        public byte[] RowVersion { get; set; }
    
        [ForeignKey("CreatedBy")]
        public ApplicationUser ApplicationCreatedUser { get; set; }
    
        [ForeignKey("LastModifiedBy")]
        public ApplicationUser ApplicationLastModifiedUser { get; set; }
    
    
    }
    

    }

    在我们的Razorpage中,我们有一个页面通过执行以下操作删除TournamentBatch,包括TournamentBatchItem:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using Microsoft.EntityFrameworkCore;
    using AthlosifyWebArchery.Data;
    using AthlosifyWebArchery.Models;
    using Microsoft.Extensions.Logging;
    
    namespace AthlosifyWebArchery.Pages.Administrators.TournamentBatches
    {
      public class DeleteModel : PageModel
       {
        private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;
    
    
        private readonly ILogger _logger;
    
    
        public DeleteModel(AthlosifyWebArchery.Data.ApplicationDbContext context,
                            ILogger<DeleteModel> logger)
        {
            _context = context;
            _logger = logger;
        }
    
        [BindProperty]
        public TournamentBatch TournamentBatch { get; set; }
    
        public IList<TournamentBatchItem> tournamentBatchItems { get; set; }
    
        public string ConcurrencyErrorMessage { get; set; }
    
        public async Task<IActionResult> OnGetAsync(Guid? id, bool? concurrencyError)
        {
            if (id == null)
            {
                return NotFound();
            }
    
            TournamentBatch = await _context.TournamentBatch
                                        .AsNoTracking() //Addded
                                        .FirstOrDefaultAsync(m => m.TournamentBatchID == id);
    
    
    
            if (TournamentBatch == null)
            {
                return NotFound();
            }
    
            if (concurrencyError.GetValueOrDefault())
            {
                ConcurrencyErrorMessage = "The record you attempted to delete "
                  + "was modified by another user after you selected delete. "
                  + "The delete operation was canceled and the current values in the "
                  + "database have been displayed. If you still want to delete this "
                  + "record, click the Delete button again.";
            }
    
            return Page();
        }
    
        public async Task<IActionResult> OnPostAsync(Guid? id)
        {
            try
            {
                //var tournamentBatchItems = await _context.TournamentBatchItem.Where(m => m.TournamentBatchID == id).ToListAsync();
                //_context.TournamentBatchItem.RemoveRange(tournamentBatchItems);
                //await _context.SaveChangesAsync();
    
    
                if (await _context.TournamentBatch.AnyAsync(
                    m => m.TournamentBatchID == id))
                {
                    // Department.rowVersion value is from when the entity
                    // was fetched. If it doesn't match the DB, a
                    // DbUpdateConcurrencyException exception is thrown.
                    _context.TournamentBatch.Remove(TournamentBatch);
                    _logger.LogInformation($"TournamentBatch.BeforeSaveChangesAsync ... ");
                    await _context.SaveChangesAsync();
                    _logger.LogInformation($"DbInitializer.AfterSaveChangesAsync ... ");
                }
                return RedirectToPage("./Index");
            }
            catch(DbUpdateException)
            {
                return RedirectToPage("./Delete",
                    new { concurrencyError = true, id = id });
    
            }
            //catch (DbUpdateConcurrencyException)
            //{
            //    return RedirectToPage("./Delete",
            //        new { concurrencyError = true, id = id });
            //}
        }
    }
    

    }

    ...,并且出现以下错误,这有点奇怪。



    有任何想法吗?

    我们所做的事情:
  • 如果我们从OnBeforeSaving();方法中删除了SaveChangesAsyc(),则该代码是删除(硬删除)成功的TournamentBatch和TournamentBatchItem。
  • 如果我们从OnBeforeSaving();方法中包括了SaveChangesAsyc()并通过删除主机 TournamentBatchItem (不是 TournamentBatch )进行了测试,则该代码已被成功删除(软删除)。

  • 似乎与Host和TournamentBatch之间的关系有关

    环境:
  • .Net Core 2.1
  • SQL Server女士
  • 最佳答案

    原因

    我想原因是您从客户端绑定(bind)了TournamentBatch

    让我们回顾OnPostAsync()方法:

    public async Task<IActionResult> OnPostAsync(Guid? id)
    {
        try
        {
            if (await _context.TournamentBatch.AnyAsync(
                m => m.TournamentBatchID == id))
            {
                _context.TournamentBatch.Remove(TournamentBatch);
                _logger.LogInformation($"TournamentBatch.BeforeSaveChangesAsync ... ");
                await _context.SaveChangesAsync();
                _logger.LogInformation($"DbInitializer.AfterSaveChangesAsync ... ");
            }
            return RedirectToPage("./Index");
        }
        // ....
    }
    

    在这里TournamentBatch是PageModel 的属性:
        [BindProperty]
        public Models.TournamentBatch TournamentBatch{ get; set; }
    

    请注意,您没有根据ID 从数据库中检索它,而只是,可以通过_context.TournamentBatch.Remove(TournamentBatch);直接将其删除

    换句话说,TournamentBatch的其他属性将由ModelBinding设置。假设如果您仅提交ID,则所有其他属性均为默认值。例如,Host将为null,并且HostID将为默认的00000000-0000-0000-0000-000000000000。因此,当您保存更改时,EF Core将更新模型,如下所示:
    UPDATE [TournamentBatch]
    SET [HostID] = '00000000-0000-0000-0000-000000000000' ,
        [IsDeleted] = 1 ,
        # ... other fields
    WHERE [TournamentBatchID] = 'A6F5002A-60CA-4B45-D343-08D660167B06'
    

    因为没有ID为00000000-0000-0000-0000-000000000000的Host记录,所以数据库将提示:



    如何修复

    代替从客户端绑定(bind)TournamentBatch,您需要通过TournamentBatch从服务器检索TournamentBatch = await _context.TournamentBatch.FindAsync(id);。因此,您将正确设置所有属性,以便EF可以正确更新字段:
        try
        {
            //var tournamentBatchItems = await _context.TournamentBatchItem.Where(m => m.TournamentBatchID == id).ToListAsync();
            //_context.TournamentBatchItem.RemoveRange(tournamentBatchItems);
            //await _context.SaveChangesAsync();
            TournamentBatch = await _context.TournamentBatch.FindAsync(id);
    
            if (TournamentBatch != null)
            {
                // Department.rowVersion value is from when the entity
                // was fetched. If it doesn't match the DB, a
                // DbUpdateConcurrencyException exception is thrown.
                _context.TournamentBatch.Remove(TournamentBatch);
                _logger.LogInformation($"TournamentBatch.BeforeSaveChangesAsync ... ");
                await _context.SaveChangesAsync();
                _logger.LogInformation($"DbInitializer.AfterSaveChangesAsync ... ");
            }
            return RedirectToPage("./Index");
        }
        // ...
    

    10-08 14:17