it-swarm.com.de

Wie kann ich mit Entity Framework automatisch gelöschte Entitäten herausfiltern?

Ich verwende zuerst den Entity Framework Code. Ich überschreibe SaveChanges in DbContext, um ein "soft delete" durchführen zu können:

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

    continue;
}

Das ist großartig, daher weiß das Objekt, wie es sich selbst als weiches Löschen kennzeichnet (in diesem Fall setzt es einfach IsDeleted auf true).

Meine Frage ist, wie kann ich es so machen, dass ich jedes Objekt mit IsDeleted ignoriere, wenn ich das Objekt abrufe? Wenn ich also _db.Users.FirstOrDefault(UserId == id) sagte, wenn dieser Benutzer IsDeleted == true hätte, würde er dies ignorieren. Im Wesentlichen möchte ich filtern? 

Hinweis: Ich möchte nicht einfach && IsDeleted == true .__ setzen. Deshalb kennzeichne ich die Klassen mit einer Schnittstelle, so dass das Entfernen von "Just Work" funktioniert und ich den Abruf irgendwie ändern möchte, um zu wissen, wie "Just Work "auch basierend auf dieser Schnittstelle.

31
Jordan

Ich habe Soft-Delete für alle meine Entitäten erhalten, und Soft-Delete-Elemente werden nicht über den Kontext abgerufen, wobei eine von diese Antwort vorgeschlagene Technik verwendet wird. Dazu gehört, wenn Sie über Navigationsmerkmale auf die Entität zugreifen.

Fügen Sie jeder Entität, die gelöscht werden kann, einen IsDeleted-Diskriminator hinzu. Leider habe ich nicht herausgefunden, wie dieses Bit basierend auf der Entität ausgeführt wird, die von einer abstrakten Klasse oder einem Interface stammt ( EF-Mapping unterstützt derzeit keine Interfaces als Entität ):

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);
            });
}

Überschreiben Sie SaveChanges und suchen Sie alle Einträge, die gelöscht werden sollen:

EditEine andere Möglichkeit, die Lösch-SQL zu überschreiben } ist das Ändern der von EF6 generierten gespeicherten Prozeduren

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();
}

Die SoftDelete-Methode führt sql direkt in der Datenbank aus, da Diskriminator-Spalten nicht in Entitäten eingeschlossen werden können:

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 gibt die zu aktualisierende Tabelle für eine Entität zurück. Es behandelt den Fall, bei dem die Tabelle nicht mit einem abgeleiteten Typ, sondern mit dem BaseType verknüpft ist. Ich habe den Verdacht, dass ich die gesamte Vererbungshierarchie überprüfen sollte .... Es gibt jedoch Pläne, die Metadaten-API zu verbessern und ob ich mich mit EF-Code erste Zuordnung zwischen Typen & Tabellen

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 );
}

Ich hatte zuvor Indizes zu natürlichen Schlüsseln in einer Migration mit Code erstellt, der so aussah:

public override void Up()
{
    CreateIndex("dbo.Organisations", "Name", unique: true, name: "IX_NaturalKey");
}

Das bedeutet jedoch, dass Sie keine neue Organisation mit demselben Namen wie eine gelöschte Organisation erstellen können. Um dies zu ermöglichen, habe ich den Code geändert, um die Indizes so zu erstellen:

public override void Up()
{
    Sql(String.Format("CREATE UNIQUE INDEX {0} ON dbo.Organisations(Name) WHERE IsDeleted = 0", "IX_NaturalKey"));
}

Und das schließt gelöschte Elemente aus dem Index aus

Note Wenn die Navigationseigenschaften nicht gefüllt werden, wenn das zugehörige Element gelöscht wird, lautet der Fremdschlüssel . Beispiel:

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;

P.S. Stimmen Sie für die globale Filterung hier https://entityframework.codeplex.com/workitem/945?FocusElement=CommentTextBox# und die gefilterten Einschlüsse hier

35
Colin

Verwenden Sie EntityFramework.DynamicFilters . Hier können Sie globale Filter erstellen, die automatisch (einschließlich der Navigationseigenschaften) angewendet werden, wenn Abfragen ausgeführt werden. 

Auf der Projektseite gibt es einen Beispielfilter "IsDeleted", der folgendermaßen aussieht:

modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);

Dieser Filter fügt automatisch eine where-Klausel für jede Abfrage gegen eine Entität ein, die ISoftDelete ist. Filter werden in DbContext.OnModelCreating () definiert.

Haftungsausschluss: Ich bin der Autor.

35
John

Eine Möglichkeit wäre, den !IsDeleted in eine Erweiterungsmethode einzukapseln. Etwas wie unten ist nur ein Beispiel. Hüten Sie sich vor, um Ihnen eine Vorstellung von einer Erweiterungsmethode zu geben, die unten nicht kompiliert werden kann.

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);
    }
}

Verwendungszweck:  

_db.Users.FirstOrDefaultExcludingDeletes(UserId == id)
7
Ricky G

Sie können Globale Abfragefilter in Entity Framework Core 2.0 verwenden.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");

    // Configure entity filters
    modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
    modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}
0
hkutluay