我有一个 ASP.NET Core Web API 项目,该项目有一个
Category
对象,该对象与其自身具有父子关系。
我创建了以下父子关系:
"Pets" -> "Pet Food" -> "Dog Food" -> "Ugly Dog Food"
我想删除“Dog Food”,但首先我以编程方式将“Pet Food”设为“Ugly Dog Food”的父类别,这样“Ugly Dog Food”就不会被孤立。
我成功更新,然后触发 Web API 调用以删除“Dog Food”。
当我尝试这样做时,出现以下错误:
详细信息:密钥(Id)=(0c6b1a97-d035-40ac-aa1f-ceef7144a236)仍然从表“类别”中引用。
在Npgsql.Internal.NpgsqlConnector.ReadMessageLong(布尔异步,DataRowLoadingMode dataRowLoadingMode,布尔readingNotifications,布尔isReadingPrepishedMessage)
在 System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 令牌)1.StateMachineBox
在 Npgsql.NpgsqlDataReader.NextResult(布尔异步,布尔 isConsuming,CancellationToken 取消令牌)
在 Npgsql.NpgsqlDataReader.NextResult(布尔异步,布尔 isConsuming,CancellationToken 取消令牌)
在 Npgsql.NpgsqlCommand.ExecuteReader(布尔异步,CommandBehavior 行为,CancellationToken 取消令牌)
在 Npgsql.NpgsqlCommand.ExecuteReader(布尔异步,CommandBehavior 行为,CancellationToken 取消令牌)
在 Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior 行为,CancellationToken 取消令牌)
在Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject参数对象,CancellationToken取消令牌)
在Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject参数对象,CancellationToken取消令牌)
在 Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection 连接,CancellationToken 取消令牌)异常数据:
严重性:错误
sql状态:23503
MessageText:表“类别”上的更新或删除违反了表“类别”上的外键约束“FK_Category_Category_ParentId”
详细信息:密钥(Id)=(0c6b1a97-d035-40ac-aa1f-ceef7144a236)仍然从表“类别”中引用。
但是我检查了数据库,“丑陋的狗粮”已更新为“宠物食品”的
ParentId
,并且表中没有其他类别条目将“狗粮”的 id 作为其 ParentId
,所以这必须是 Entity Framework Core 的缓存。
为什么我更新“丑狗粮”的
parentId
时缓存没有更新?
这是我的
CategoryService
课程。对于更新,我在此处使用“丑陋的狗粮”调用 UpdateCategory
方法,然后在成功返回更新后,我在“狗粮”上启动 DeleteCategory
方法:
using ShopBack.Database.Repositories;
using ShopBack.Database;
namespace ShopBack.Services;
public class CategoryService
{
private readonly ICategoryRepository _categoryRepository;
private readonly IShopUnitOfWork _unitOfWork;
public CategoryService(IShopUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_categoryRepository = unitOfWork.Categories;
}
/// <summary>
/// Call to create category in the CategoryRepository
/// </summary>
/// <param name="category">The category entity</param>
/// <returns>void</returns>
public async Task CreateCategory(Category category)
{
await _categoryRepository.AddAsync(category);
await _unitOfWork.SaveAsync();
}
/// <summary>
/// Call to get all categories in the CategoryRepository
/// </summary>
/// <returns>void</returns>
public async Task<IEnumerable<Category>> GetAllCategories()
{
return await _categoryRepository.GetAllAsync();
}
/// <summary>
/// Call to get all categories in the CategoryRepository
/// </summary>
/// <returns>void</returns>
public async Task<IEnumerable<Category>> GetAllTopCategories()
{
return await _categoryRepository.getAllTopLevelCategories();
}
/// <summary>
/// Call to update Category in the CategoryRepository
/// </summary>
/// <param name="Category">The Category entity</param>
/// <returns>void</returns>
public async Task UpdateCategory(Category category)
{
_categoryRepository.Update(category);
await _unitOfWork.SaveAsync();
}
/// <summary>
/// Call to get a single Category in the CategoryRepository
/// </summary>
/// <param name="categoryId">The Category Id</param>
/// <returns>Category</returns>
public async Task<Category?> GetCategory(Guid categoryId)
{
return await _categoryRepository.GetByIdAsync(categoryId);
}
/// <summary>
/// Call to delete Category in the CategoryRepository
/// </summary>
/// <param name="category">The Category entity</param>
/// <returns>void</returns>
public async Task DeleteCategory(Category category)
{
//todo what if the category has already been deleted? What happens?
_categoryRepository.Delete(category);
await _unitOfWork.SaveAsync();
}
}
这是我的
BaseRepository
课程:
using Microsoft.EntityFrameworkCore;
namespace ShopBack.Database.Repositories
{
public class BaseRepository<T> : IRepositoryBase<T> where T : ModelBase
{
protected readonly LibraryContext _context;
public BaseRepository(LibraryContext context)
{
//context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
_context = context;
}
/// <summary>
/// Gets all entities for this particular repository
/// </summary>
public virtual async Task<IEnumerable<T>> GetAllAsync()
{
return await _context.Set<T>().ToListAsync();
}
/// <summary>
/// Gets an entity by Id for this particular repository
/// </summary>
public virtual async Task<T?> GetByIdAsync(Guid id)
{
return await _context.Set<T>().FindAsync(id);
}
/// <summary>
/// Adds an an entity by for this particular repository
/// </summary>
public virtual async Task AddAsync(T entity)
{
await _context.Set<T>().AddAsync(entity);
}
/// <summary>
/// Updates an entity by for this particular repository
/// </summary>
public virtual void Update(T entity)
{
_context.Entry(entity).State = EntityState.Modified;
}
/// <summary>
/// Deletes an entity for this particular repository
/// </summary>
public virtual void Delete(T entity)
{
_context.Set<T>().Remove(entity);
}
/// <summary>
/// Checks to see if an entity exists for this particular repository
/// </summary>
public virtual async Task<bool> ExistsAsync(Guid id)
{
return await _context.Set<T>().AnyAsync(x => x.Id == id);
}
}
}
这是我的
UnitOfWork
课程:
using ShopBack.Database.Repositories;
namespace ShopBack.Database
{
public class UnitOfWork : IUnitOfWork
{
private readonly LibraryContext _context;
private Dictionary<Type, object> _repositories;
/// <summary>
///Constructor for our UnitOfWork class
/// </summary>
public UnitOfWork(LibraryContext context)
{
_context = context;
_repositories = new Dictionary<Type, object>();
}
public void Dispose()
{
_context.Dispose();
}
TRepository IUnitOfWork.GetRepository<TRepository, TEntity>()
{
var type = typeof(TEntity);
if (_repositories.ContainsKey(type))
{
return (TRepository)_repositories[type];
}
else
{
var repositoryType = typeof(TRepository);
var repGenType = repositoryType.MakeGenericType(typeof(TEntity));
var repositoryInstance = Activator.CreateInstance(repGenType, _context);
_repositories.Add(type, repositoryInstance);
}
return (TRepository)_repositories[type];
}
/// <summary>
/// Persists our changes
/// </summary>
public async Task SaveAsync()
{
await _context.SaveChangesAsync();
}
}
}
当我进行更新时,Entity Framework Core 的缓存是否应该已更新,因为我将其状态设置为已修改,然后在
SaveChangesAsync
上运行 UnitOfWork
?
我强烈建议放弃通用存储库。这是 EF 的反模式,因为 EF 已分别在
DbSet
和 DbContext
中提供了这一点。拥有返回 IEnumerable<T>
的存储库包装器意味着每个查询都会在每次调用时将整个表具体化到内存中,这是极其浪费和缓慢的。相反,通过 IQueryable
使用 EF 的 DbSet
并利用 EF 提供的所有内容,而不是采用会严重削弱性能的抽象。
EF 已经自动管理 FK 关系,因此移动类别非常简单:
var categoryToDelete = _context.Categories
.Include(x => x.Parent)
.Include(x => x.Children)
.Single(x => x.CategoryId == categoryId);
foreach (var child in categoryToDelete.Children)
categoryToDelete.Parent.Children.Add(child);
categoryToDelete.Parent.Children.Remove(categoryToDelete);
_context.SaveChanges();
_context.Entry(categoryToDelete.Parent).State = EntityState.Detached;
最后分离父级的步骤很简单,因为在这个示例中,当我们向父级加载要删除的项目时,我们没有加载父级的完整子级列表。该父级的子级集合将仅包含我们从已删除项目中移动的项目,而不包含任何其他现有子类别。如果您稍后从 DbContext 获取父级(例如发送到视图),则可能会提供此不完整的父级。
如果您想与家长一起做某事并全面了解家长的状态:
var categoryToDelete = _context.Categories
.Include(x => x.Children)
.Single(x => x.CategoryId == categoryId);
var parent = _context.Categories
.Include(x => x.Children)
.Single(x => x.CategoryId == categoryToDelete.ParentId);
foreach (var child in categoryToDelete.Children)
parent.Children.Add(child);
parent.Children.Remove(categoryToDelete);
_context.SaveChanges();
这可确保填充父级的完整子级集合。