36

Entity Framework Code First を使用しています。「ソフト削除」を実行できるようにオーバーライドSaveChangesします。DbContext

if (item.State == EntityState.Deleted && typeof(ISoftDelete).IsAssignableFrom(type))
{
    item.State = EntityState.Modified;
    item.Entity.GetType().GetMethod("Delete")
        .Invoke(item.Entity, null);

    continue;
}

これは素晴らしいので、オブジェクトは自分自身を論理的な削除としてマークする方法を知っています (この場合は に設定IsDeletedするだけtrueです)。

私の質問は、オブジェクトを取得するときにオブジェクトを無視するようにするにはどうすればよいIsDeletedですか? _db.Users.FirstOrDefault(UserId == id)したがって、そのユーザーがそれを持っているかどうかと言ったら、IsDeleted == trueそれを無視します。本質的に私はフィルタリングしたいですか?

注:私はただ置きたくない&& IsDeleted == true ので、インターフェイスでクラスをマークしているので、削除は「ただ働く」方法を知っています。そのインターフェースが存在します。

4

5 に答える 5

40

すべてのエンティティに対して論理的な削除が機能しており、論理的に削除されたアイテムは、この回答で提案されている手法を使用してコンテキストを介して取得されません。これには、ナビゲーション プロパティを介してエンティティにアクセスする場合が含まれます。

論理的に削除できるすべてのエンティティに 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#およびフィルタリングされたインクルードはこちら

于 2013-09-24T15:28:44.027 に答える
9

1 つのオプションは!IsDeleted、 を拡張メソッドにカプセル化することです。以下のようなものはほんの一例です。拡張メソッドのアイデアを提供するためだけに注意してください。以下はコンパイルされません。

public static class EnumerableExtensions
{
    public static T FirstOrDefaultExcludingDeletes<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
        return source.Where(args => args != IsDeleted).FirstOrDefault(predicate);
    }
}

使用法:

_db.Users.FirstOrDefaultExcludingDeletes(UserId == id)
于 2012-10-02T23:08:23.643 に答える
-5

素晴らしい質問です。

なんらかの方法で実行される前にSQLクエリをインターセプトしてから、where句を追加して、「削除された」アイテムを選択から削除する必要があります。残念ながら、エンティティにはクエリの変更に使用できるGetCommandがありません。

おそらく、適切な場所にあるEFプロバイダーラッパーを変更して、クエリを変更できるようにすることができます。

または、uはQueryInterceptorを利用できますが、各クエリは InterceptWith(visitor)式を変更するために使用する必要があります。

したがって、AFAIKには他のオプションがないため、このアプローチに集中し、クエリをインターセプトして修正します(クエリを変更しないコードを保持したい場合)。

とにかく、何か役に立つものがあれば教えてください。

于 2012-10-04T14:15:41.247 に答える