Self Tracking Entities mit Code Only implementieren

Zur Zeit muss man leider zwischen Self Tracking Entities und Code Only entscheiden. Was aber, wenn man Code Only in einer verteilten Umgebung einsetzen möchte? In diesem Fall wäre es wünschenswert, die Vorteile beider Ansätze miteinander zu verbinden. Das nachfolgende Beispiel zeigt, wie dies bewerkstelligt werden kann. Der erste Teil beinhaltet ein paar allgemeine Konstrukte; der zweite Teil zeigt die Anwendung dieser Konstrukte anhand eines einfachen Beispiels.
// --- Wiederverwendbarer Teil --------------

public enum DetachedEntityState
{
    New, Updated, Deleted, Unchanged
}

public interface IEntity
{
    DetachedEntityState State { get; set; }
}

public static class StateTrackingHelper
{

    public static void ApplyStates<T>(this ICollection<T> entities, DbContext ctx) where T : class, IEntity 
    {
        foreach (var e in entities.ToArray())
        {
            ApplyState(e, ctx);
        }
    }

    public static void ApplyState<T>(this T entity, DbContext ctx) where T : class, IEntity
    {
        switch (entity.State)
        {
            case DetachedEntityState.New:
                ctx.Entry<T>(entity).State = System.Data.EntityState.Added;
                break;
            case DetachedEntityState.Updated:
                ctx.Entry<T>(entity).State = System.Data.EntityState.Modified;
                break;
            case DetachedEntityState.Deleted:
                ctx.Entry<T>(entity).State = System.Data.EntityState.Deleted;
                break;
        }
    }

    public static ICollection<T> ResetStates<T>(this ICollection<T> entities) where T : class, IEntity
    {
        var deleted = new List<T>();

        foreach (var e in entities)
        {
            if (e.State != DetachedEntityState.Deleted)
            {
                ResetState(e);
            }
        }

        return entities;
    }

    public static void ResetState<T>(this T entity) where T : class, IEntity
    {
        entity.State = DetachedEntityState.Unchanged;
    }
        
}

// --- Anwendung --------------

public class Region: IEntity
{
    public Region()
    {
        Hotels = new List<Hotel>();
        Top10Hotels = new List<Hotel>();
    }

    private int _RegionId;
    public int RegionId
    {
        get
        {
            return _RegionId;
        }
        set
        {
            if (_RegionId != value) {
                _RegionId = value;
                this.State = DetachedEntityState.Updated; 
            }
        }
    }
    private string _Bezeichnung;
    public string Bezeichnung
    {
        get
        {
            return _Bezeichnung;
        }
        set
        {
            if (_Bezeichnung != value) {
                _Bezeichnung = value;
                this.State = DetachedEntityState.Updated; 
            }
        }
    }

    private ICollection<Hotel> _Hotels;

    public virtual ICollection<Hotel> Hotels
    {
        get
        {
            return _Hotels;
        }
        set
        {
            if (_Hotels != value) {
                _Hotels = value;
                this.State = DetachedEntityState.Updated; 
            }

        }
    }
    private ICollection<Hotel> _Top10Hotels;
    public virtual ICollection<Hotel> Top10Hotels
    {
        get
        {
            return _Top10Hotels;
        }
        set
        {
            if (_Top10Hotels != value) {
                _Top10Hotels = value;
                this.State = DetachedEntityState.Updated; 
            }

                 
        }
    }

    private DetachedEntityState _State;
    [NotMapped]
    public DetachedEntityState State
    {
        get
        {
            return _State;
        }
        set
        {
            _State = value;
        }
    }
}

public class Hotel : IEntity
{
    private int _HotelId;
    public int HotelId
    {
        get
        {
            return _HotelId;
        }
        set
        {

            if (_HotelId != value) {
                _HotelId = value;
                this.State = DetachedEntityState.Updated; 
            }

            
        }
    }
    private string _Bezeichnung;
    public string Bezeichnung
    {
        get
        {
            return _Bezeichnung;
        }
        set
        {
            if (_Bezeichnung != value) {
                _Bezeichnung = value;
                this.State = DetachedEntityState.Updated; 
            }
                
        }
    }
    private int _Sterne;
    public int Sterne
    {
        get
        {
            return _Sterne;
        }
        set
        {

            if (_Sterne != value) {
                _Sterne = value;
                this.State = DetachedEntityState.Updated; 
            }
        }
    }

    private int _RegionId;
    public int RegionId
    {
        get
        {
            return _RegionId;
        }
        set
        {
            if (_RegionId != value) {
                _RegionId = value;
                this.State = DetachedEntityState.Updated; 
            }
        }
    }

    private Region _Region;
    [ForeignKey("RegionId"), InverseProperty("Hotels")]
    public virtual Region Region
    {
        get
        {
            return _Region;
        }
        set
        {
            if (_Region != value) {
                _Region = value;
                this.State = DetachedEntityState.Updated; 
            }
        }
    }

    private DetachedEntityState _State;
    [NotMapped]
    public DetachedEntityState State
    {
        get
        {
            return _State;
        }
        set
        {
            _State = value;
        }
    }
        
}

public class WellnessHotel : Hotel
{
    private int _AnzahlSaunen;
    public int AnzahlSaunen
    {
        get
        {
            return _AnzahlSaunen;
        }
        set
        {
            if (_AnzahlSaunen != value) {
                _AnzahlSaunen = value;
                this.State = DetachedEntityState.Updated; 
            }
                
        }
    }
    private int _AnzahlThermalBecken;
    public int AnzahlThermalBecken
    {
        get
        {
            return _AnzahlThermalBecken;
        }
        set
        {
            if (_AnzahlThermalBecken != value) {
                _AnzahlThermalBecken = value;
                this.State = DetachedEntityState.Updated; 
            }
        }
    }
}

public class HotelDbContext : DbContext
{
    public HotelDbContext(): base("CodeOnlyHotelDb") { } 

    public DbSet<Hotel> Hotels { get; set; }
    public DbSet<Region> Regionen { get; set; }
}

public class RegionDAO
{

    public List<Region> FindAll()
    {
        using (var ctx = new HotelDbContext())
        {
            var result = ctx.Regionen.Include("Hotels").ToList();
            result.ResetStates();
            return result;
        }

    }

    public void UpdateRegion(Region r)
    {
        using (var ctx = new HotelDbContext())
        {
            ctx.Regionen.Attach(r);

            r.ApplyState(ctx);
            r.Hotels.ApplyStates(ctx);
                
            ctx.SaveChanges();

            r.ResetState();
            r.Hotels.ResetStates();
        }
    }

    public void SaveRegion(Region r1)
    {
        using (var ctx = new HotelDbContext())
        {
            ctx.Regionen.Add(r1);
            ctx.SaveChanges();
        }
    }



}

class Program
{
    static void Main(string[] args)
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<HotelDbContext>());

        CreateSaveModifySaveDemo();
        LoadShowModifySaveDemo();
        LoadShowModifySaveDemo();

        Console.WriteLine("fertig!");
        Console.ReadLine();

    }

    private static void CreateSaveModifySaveDemo()
    {
        var dao = new RegionDAO();

        Region r1 = new Region { Bezeichnung = "Graz" };
        Hotel h1 = new Hotel { Bezeichnung = "Hotel zur Post", Sterne = 2 };

        WellnessHotel h2 = new WellnessHotel
        {
            Bezeichnung = "Hotel zur Wellness",
            Sterne = 4,
            AnzahlSaunen = 3,
            AnzahlThermalBecken = 4
        };

        r1.Hotels.Add(h1);
        r1.Hotels.Add(h2);
        r1.Top10Hotels.Add(h2);

        dao.SaveRegion(r1);

        h2.Sterne++;
        r1.Bezeichnung += "!"; // kleine Änderung

        dao.UpdateRegion(r1);
    }

    private static void LoadShowModifySaveDemo()
    {
        var dao = new RegionDAO();

        var regionen = dao.FindAll();
        foreach (var r in regionen)
        {
            Console.WriteLine(r.Bezeichnung);
            r.Bezeichnung += "$"; // kleine Änderung
            int i = 0;
            foreach (var h in r.Hotels)
            {
                Console.WriteLine("    " + h.Bezeichnung + ", Sterne: " + h.Sterne);
                if (i++ % 2 == 0)
                {
                    h.Bezeichnung += "$"; // kleine Änderung
                }
                else
                {
                    h.State = DetachedEntityState.Deleted;
                }
            }
            Console.WriteLine();
            dao.UpdateRegion(r);
        }
            
    }

}
Hinweis: Nach dem Laden der Daten muss einmal Reset, wie oben gezeigt, aufgerufen werden, da EF die Entitäten im Zuge des Materialisierens verändert bzw. weil der Standardwert von DetachedEntityState der Wert New ist.

 

Schulung und Beratung

Modern Web mit Angular 2

Datenbindung, Formulare, Validierung, Routing, HTTP, Komponenten, ...

Details

Migration auf Angular 2

Bestehende Projekte auf Angular 2 migrieren, ngUpgrade, ...

Details

Progressive Web-Apps mit Angular 2

InHouse-Schulung und/oder Beratung maßgeschneidert für Ihre Lernziele

Details

Architektur-Workshop

Interaktiver Prototyp-Workshop für Ihre Anwendung

Details

Entity Framework (EF)

Datenzugriff mit Entity Framework, Mapping-Szenarien, CRUD, Transaktionen, Migrations, Stored Procedures, Vererbung, Neuerungen in Version 7

Details

Angular 2: Deep Dive

Erweiterte Aspekte von Angular 2

Details

ASP.NET WebAPI

Web APIs mit ASP.NET, HTTP, REST, Security, Formatter, Tracing, OData, Streaming

Details

Web APIs mit ASP.NET MVC 6

Web APIs mit ASP.NET, HTTP, REST, Security, Formatter, Tracing, OData, Streaming

Details

Moderne Security-Szenarien für Web APIs

OAuth 2, OpenId Connect, JWT, Spielarten und Flows, ...

Details

Weitere Schulungen ...