【发布时间】:2019-05-09 17:17:59
【问题描述】:
我们有 3 个模型类:
- 主持人
- TournamentBatch
- TournamentBatchItem
Host 有很多 TournamentBatch。 TournamentBatch 有很多 TournamentBatchItem。在 TournamentBatch 表中会有 FK Host。
我们确实覆盖了 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 });
//}
}
}
}
... 我们有以下错误,这有点奇怪。
System.Data.SqlClient.SqlException (0x80131904):UPDATE 语句 与 FOREIGN KEY 约束冲突 “FK_TournamentBatch_Host_HostID”。数据库发生冲突 "aspnet-AthlosifyWebArchery-53bc9b9d-9d6a-45d4-8429-2a2761773502", 表“dbo.Host”,列“HostID”。声明已终止。
有什么想法吗?
我们做过的事情:
如果我们从
SaveChangesAsyc()方法中删除OnBeforeSaving();,代码将成功删除(硬删除) TournamentBatch 以及 TournamentBatchItem。如果我们从
SaveChangesAsyc()方法中包含OnBeforeSaving();并通过删除 Host 和 TournamentBatchItem 进行测试(不是 TournamentBatch) ,代码删除(软删除)成功。
好像和Host和TournamentBatch的关系有关
环境:
- .Net Core 2.1
- SQL Server 女士
【问题讨论】:
-
我试过你的代码,但我没有抛出异常。但是,错误信息表明您的代码正在后台更新外键。我建议您可以检查客户端发送的有效负载并检查 sql 日志。
-
@itminus ...不太明白,因为我们根本没有更新外键。正如您在我们的代码中看到的那样。
-
发帖到action方法时,
TournamentBatch是什么? -
它是 ApplicationDbContext 类的属性。
public DbSet<AthlosifyWebArchery.Models.TournamentBatch> TournamentBatch { get; set; } -
我的意思是当您调用`await _context.SaveChangesAsync();` 时,TournamentBatch 或其他实体必须有一些更改,对吧?那么这些实体是什么样的呢?而当你保存更改并抛出异常时,幕后的sql语句是什么?
标签: c# asp.net-core entity-framework-core asp.net-core-2.0 asp.net-core-2.1