すべてのエンティティに対して論理的な削除が機能しており、論理的に削除されたアイテムは、この回答で提案されている手法を使用してコンテキストを介して取得されません。これには、ナビゲーション プロパティを介してエンティティにアクセスする場合が含まれます。
論理的に削除できるすべてのエンティティに IsDeleted 識別子を追加します。残念ながら、抽象クラスまたはインターフェイスから派生したエンティティに基づいてこのビットを実行する方法を考え出していません (EF マッピングは現在、インターフェイスをエンティティとしてサポートしていません)。
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>().Map(m => m.Requires("IsDeleted").HasValue(false));
modelBuilder.Entity<Bar>().Map(m => m.Requires("IsDeleted").HasValue(false));
//It's more complicated if you have derived entities.
//Here 'Block' derives from 'Property'
modelBuilder.Entity<Property>()
.Map<Property>(m =>
{
m.Requires("Discriminator").HasValue("Property");
m.Requires("IsDeleted").HasValue(false);
})
.Map<Block>(m =>
{
m.Requires("Discriminator").HasValue("Block");
m.Requires("IsDeleted").HasValue(false);
});
}
SaveChanges をオーバーライドし、削除するすべてのエントリを見つけます。
編集
削除 SQL をオーバーライドする別の方法は、EF6 によって生成されたストアド プロシージャを変更することです。
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries()
.Where(p => p.State == EntityState.Deleted
&& p.Entity is ModelBase))//I do have a base class for entities with a single
//"ID" property - all my entities derive from this,
//but you could use ISoftDelete here
SoftDelete(entry);
return base.SaveChanges();
}
SoftDelete メソッドは、識別子列をエンティティに含めることができないため、SQL をデータベースで直接実行します。
private void SoftDelete(DbEntityEntry entry)
{
var e = entry.Entity as ModelBase;
string tableName = GetTableName(e.GetType());
Database.ExecuteSqlCommand(
String.Format("UPDATE {0} SET IsDeleted = 1 WHERE ID = @id", tableName)
, new SqlParameter("id", e.ID));
//Marking it Unchanged prevents the hard delete
//entry.State = EntityState.Unchanged;
//So does setting it to Detached:
//And that is what EF does when it deletes an item
//http://msdn.microsoft.com/en-us/data/jj592676.aspx
entry.State = EntityState.Detached;
}
GetTableName は、エンティティに対して更新されるテーブルを返します。テーブルが派生型ではなく BaseType にリンクされている場合を処理します。継承階層全体をチェックする必要があると思います....しかし、メタデータ API を改善する計画があり、必要に応じて、型とテーブル間の EF Code First Mappingを調べます。
private readonly static Dictionary<Type, EntitySetBase> _mappingCache
= new Dictionary<Type, EntitySetBase>();
private ObjectContext _ObjectContext
{
get { return (this as IObjectContextAdapter).ObjectContext; }
}
private EntitySetBase GetEntitySet(Type type)
{
type = GetObjectType(type);
if (_mappingCache.ContainsKey(type))
return _mappingCache[type];
string baseTypeName = type.BaseType.Name;
string typeName = type.Name;
ObjectContext octx = _ObjectContext;
var es = octx.MetadataWorkspace
.GetItemCollection(DataSpace.SSpace)
.GetItems<EntityContainer>()
.SelectMany(c => c.BaseEntitySets
.Where(e => e.Name == typeName
|| e.Name == baseTypeName))
.FirstOrDefault();
if (es == null)
throw new ArgumentException("Entity type not found in GetEntitySet", typeName);
_mappingCache.Add(type, es);
return es;
}
internal String GetTableName(Type type)
{
EntitySetBase es = GetEntitySet(type);
//if you are using EF6
return String.Format("[{0}].[{1}]", es.Schema, es.Table);
//if you have a version prior to EF6
//return string.Format( "[{0}].[{1}]",
// es.MetadataProperties["Schema"].Value,
// es.MetadataProperties["Table"].Value );
}
以前、次のようなコードを使用して、移行で自然キーにインデックスを作成していました。
public override void Up()
{
CreateIndex("dbo.Organisations", "Name", unique: true, name: "IX_NaturalKey");
}
ただし、削除された組織と同じ名前の新しい組織を作成することはできません。これを可能にするために、コードを変更してインデックスを作成しました。
public override void Up()
{
Sql(String.Format("CREATE UNIQUE INDEX {0} ON dbo.Organisations(Name) WHERE IsDeleted = 0", "IX_NaturalKey"));
}
そして、削除されたアイテムをインデックスから除外します
関連項目が論理的に削除されている場合、ナビゲーション プロパティは設定されませんが、外部キーは設定されます。例えば:
if(foo.BarID != null) //trying to avoid a database call
string name = foo.Bar.Name; //will fail because BarID is not null but Bar is
//but this works
if(foo.Bar != null) //a database call because there is a foreign key
string name = foo.Bar.Name;
PSグローバル フィルタリングの投票はこちらhttps://entityframework.codeplex.com/workitem/945?FocusElement=CommentTextBox#およびフィルタリングされたインクルードはこちら