最初に Entity Framework Core 2.2 コードを使用しています。論理的な削除は、オーバーライドされた Context クラスに実装されています。コンストラクターでコンテキストを引数として取りながら、特定のエンティティのリポジトリを初期化する UnitOfWork クラスがあります。概要を明確にするために、このサンプルでは一部のアプリケーション エンティティが省略されています。チーム、メンバー、プロジェクト、および従業員のみを使用します。
テスト中に、次のことに気付きました: オブジェクト (たとえば、プロジェクト) を削除すると、SaveChanges() の後で、その "Deleted" プロパティが "true" に正常に設定されます。ただし、ソリューション プロジェクトがまだ実行されている場合、このオブジェクト (プロジェクト) に親オブジェクト (チーム) がある場合、「削除済み」プロパティが明らかに「true」であっても、チームのリスト プロジェクトに表示されることに気付きました。プロセスがまだ実行されている間に、すべてのプロジェクトを返すリポジトリの Get() メソッドを呼び出すと、削除されたプロジェクトは返されたプロジェクト リストには表示されず、チームのリスト プロジェクトにのみ表示されます。ソリューション プロジェクトを再実行すると、これらの子オブジェクトが親のリスト プロジェクトに表示されなくなります。
問題を分析するために作成した NUnit テストを次に示します。
[Test, Order(2)]
public void GetTeamAfterDeletingProject()
{
//There are initially 11 projects
var projects = unit.Projects.Get().ToList();
Assert.AreEqual(11, projects.Count());
Project project = unit.Projects.Get(8);
unit.Projects.Delete(project);
unit.Save();
//now there are 10 projects
projects = unit.Projects.Get().ToList();
Assert.AreEqual(10, projects.Count());
//this team had initially 2 projects, and one of them was deleted
Team team = unit.Teams.Get(3);
//only one project should be left, but there are actually two showing up - this test fails
Assert.AreEqual(1, team.Projects.Count);
project.Deleted = false;
unit.Save();
}
これは、リストに子オブジェクトがある場合、オブジェクトの削除を許可しない、TeamsRepository に実装した Delete メソッドに干渉します。つまり、以前のテストでチームを削除したい場合、彼のプロジェクトをすべて削除したとしても、アプリを再起動しないと削除できません (リポジトリ全体は以下にあります)。
public override void Delete(int id)
{
Team old = Get(id);
if (old.Projects.Count != 0 || old.Members.Count != 0)
{
Services.ThrowChildrenPresentException();
}
Delete(old);
}
これが遅延読み込みを使用している場合の通常の動作なのか、それとも構成に何かが欠けていたのか疑問に思っています。
protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder)
{
if(_conStr != null)
{
optionBuilder.UseNpgsql(_conStr);
}
optionBuilder.UseLazyLoadingProxies(true);
base.OnConfiguring(optionBuilder);
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Member>().HasQueryFilter(x => !x.Deleted);
builder.Entity<Employee>().HasQueryFilter(x => !x.Deleted);
builder.Entity<Project>().HasQueryFilter(x => !x.Deleted);
builder.Entity<Team>().HasQueryFilter(x => !x.Deleted);
}
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(x => x.State == EntityState.Deleted && x.Entity is BaseClass))
{
entry.State = EntityState.Modified;
entry.CurrentValues["Deleted"] = true;
}
return base.SaveChanges();
}
これはチーム エンティティです。
public class Team: BaseClass
{
public Team()
{
Members = new List<Member>();
Projects = new List<Project>();
}
public string Name { get; set; }
public string Description { get; set; }
public bool StatusActive { get; set; }
public virtual IList<Member> Members { get; set; }
public virtual IList<Project> Projects { get; set; }
}
public class Member: BaseClass
{
public virtual Team Team { get; set; }
public virtual Employee Employee { get; set; }
public virtual Role Role { get; set; }
public virtual MemberStatus Status { get; set; }
public decimal HoursWeekly { get; set; }
}
public class BaseClass
{
public BaseClass()
{
Created = DateTime.Now;
Creator = 0;
Deleted = false;
}
[Key]
public int Id { get; set; }
public DateTime Created { get; set; }
public int Creator { get; set; }
public bool Deleted { get; set; }
}
オーバーライドされたメソッドを持つ汎用リポジトリと特定の TeamsRepository。論理的に削除されたプロジェクトは old.Projects.Count に表示されるため、この方法でチームを削除することはできません (たとえそれらが取得されなかったとしても)
public class Repository<Entity>: IRepository<Entity> where Entity : class
{
protected AppContext _context;
protected DbSet<Entity> _dbSet;
public Repository(AppContext context)
{
_context = context;
_dbSet = _context.Set<Entity>();
}
public void ValidateUpdate(Entity newEntity, int id)
{
if (id != (newEntity as BaseClass).Id)
throw new ArgumentException($"Error! Id of the sent object: {(newEntity as BaseClass).Id} and id in url: {id} do not match");
}
public virtual IQueryable<Entity> Get() => _dbSet;
public virtual IList<Entity> Get(Func<Entity, bool> where) => Get().Where(where).ToList();
public virtual Entity Get(int id)
{
Entity entity = _dbSet.Find(id);
if (entity == null)
throw new ArgumentException($"There is no object with id: {id} in the database");
return entity;
}
public virtual void Insert(Entity entity)
{
entity.Build(_context);
_dbSet.Add(entity);
}
public virtual void Update(Entity entity, int id)
{
entity.Build(_context);
Entity old = Get(id);
ValidateUpdate(entity, id);
_context.Entry(old).CurrentValues.SetValues(entity);
old.Relate(entity);
}
public void Delete(Entity entity) => _dbSet.Remove(entity);
public virtual void Delete(int id)
{
Entity old = Get(id);
Delete(old);
}
}
public class TeamsRepository: Repository<Team>
{
public TeamsRepository(AppContext context) : base(context) { }
public override void Delete(int id)
{
Team old = Get(id);
if (old.Projects.Count != 0 || old.Members.Count != 0)
{
Services.ThrowChildrenPresentException();
}
Delete(old);
}
}