RSS FeedFeed TwitterTwitter XINGXING
  
Meine Bücher

Erscheint demnächst: Verteilte Systeme und Services mit .NET 4.5: Konzepte und Lösungen für WCF 4.5 und ASP.NET Web-API ,
Hanser Fachbuchverlag

Weitere Infos

 

.NET 4.5 Update,
Microsoft Press

Weitere Infos

 

Verteilte Systeme und Services mit .NET 4.0: Konzepte und Lösungen mit WCF 4.0,
Hanser Fachbuchverlag

Weitere Infos

 
Weitere Bücher
Meine Artikel

Leichtgewichtige Kommunikation: REST-basierte Services mit dem neuen API aus der ASP.NET-Familie,
windows.developer

Weitere Infos

 

Windows Azure Tutorial, Teil 3: Verbindung zwischen Cloud- und lokalen Applikationen,
iX - Magazin für professionelle Informationstechnik

Weitere Infos

 

Weitere Artikel

Schulung & Beratung

Gemeinsam mit meinen Kollegen aus dem IT-Visions Netzwerk unterstützte ich durch zielgerichtete Inhouse-Trainings und Consulting Unternehmen bei der Planung und Umsetzung großer Software-Systeme mit der Microsoft/.NET-Plattform.

 
Konferenzen

Scandinavian Developer Conference 2013 (SDC 2013) von 04.03.2013 bis 06.03.2013 in Göteborg

Meine Talks

Web-Site der Konferenz

 

BASTA! on Tour Spring 2013 von 24.04.2013 bis 26.04.2013 in Düsseldorf

Meine Talks

Web-Site der Konferenz

 

5th SOA and Cloud-Technology Symposium 2012 von 24.09.2012 bis 25.09.2012 in London

Meine Talks

Web-Site der Konferenz

 
Weitere Konferenzen
von Manfred, 4. April 2012 20:51

Eine Herausforderung beim Arbeiten mit abgehängten Objekten ist es, nach dem Wiederanhängen herauszufinden, welche Datensätze eines Objektgraphen sich geändert haben. Zur Lösung dieser Herausforderung habe ich eine Implementierung der aus Entity Framework bekannten Self-Tracking-Entities umgesetzt.

Dabei stellt der Enum EntityState den aktuellen Zustand einer Entität dar. Das Interface IEntity, welche lediglich eine Eigenschaft State von diesem Typ vorgibt, ist von allen Self-Tracking-Entities zu implementieren. Alternativ dazu kann man auch von der Klasse Entity, welche zusätzlich ein paar Convenience-Methoden zum Ändern und Abfragen der Eigenschaft State bietet, abgeleitet werden.

public enum EntityState
{
    Unmodified = 0,
    Modified = 1,
    Deleted = 2
}

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

public abstract class Entity : IEntity, ILifecycle
{
    public virtual EntityState State { get; set; }

    public  virtual void MarkAsDeleted()
    {
        State = EntityState.Deleted;
    }

    public virtual void MarkAsModified()
    {
        State = EntityState.Modified;
    }

    public virtual void MarkAsUnModified()
    {
        State = EntityState.Unmodified;
    }

    public virtual LifecycleVeto OnDelete(NHibernate.ISession s)
    {
        return LifecycleVeto.NoVeto;
    }

    public virtual void OnLoad(NHibernate.ISession s, object id)
    {
        State = EntityState.Unmodified;
    }

    public virtual LifecycleVeto OnSave(NHibernate.ISession s)
    {
        return LifecycleVeto.NoVeto;
    }

    public virtual LifecycleVeto OnUpdate(NHibernate.ISession s)
    {
        return LifecycleVeto.NoVeto;
    }


}
Self-Tracking-Entities sollten darüber hinaus sicherstellen, dass jeder Setter die Eigenschaft State auf Modified setzt. Hierfür bietet sich der Einsatz von Code-Generatoren an. Kann man das nicht sicherstellen, muss man diese Eigenschaft nach jeder Änderung manuell setzen, zum Beispiel im View-Model.
[Class(Name = "NHibernateSample.entities.Bestellung, NHibernateSample")]
public class Bestellung : Entity
{
    public Bestellung()
    {
        Positionen = new Iesi.Collections.Generic.HashedSet<Position>();
    }

    protected int bestellungId;


    protected DateTime bestellDatum { get; set; }

    [Id(0, Name = "BestellungId")]
    [Generator(1, Class = "hilo")]
    public virtual int BestellungId
    {
        get { return bestellungId; }
        set
        {
            bestellungId = value;
            this.MarkAsModified();
        }
    }

    [Property(0, Name = "BestellDatum")]
    public virtual DateTime BestellDatum
    {
        get { return bestellDatum; }
        set
        {
            bestellDatum = value;
            this.MarkAsModified();
        }
    }


    [Set(0, Name = "Positionen", Lazy = CollectionLazy.True,
        Cascade = "NONE", Inverse = true)]
    [Key(1, Column = "BestellungId")]
    [OneToMany(2,
        Class = "NHibernateSample.entities.Position,NHibernateSample")]
    public virtual IesiCollections.ISet<Position> Positionen { get; set; }

    public virtual void AddPosition(Position p)
    {
        Positionen.Add(p);
        p.Bestellung = this;
        this.MarkAsModified();
        // p.State = EntityState.Modified;
    }

}


    [Class(Name = "NHibernateSample.entities.Position, NHibernateSample")]
    public class Position : Entity
    {

        [Id(0, Name = "PositionId")]
        [Generator(1, Class = "native")]
        protected virtual int? PositionId { get; set; }


        protected int anzahl;

        [Property(0, Name = "Anzahl")]
        public virtual int Anzahl
        {
            get { return anzahl; }
            set
            {
                anzahl = value;
                this.MarkAsModified();
            }
        }


        private String pizza;

        [Property(0, Name = "Pizza")]
        public virtual String Pizza
        {
            get { return pizza; }
            set
            {
                pizza = value;
                this.MarkAsModified();
            }
        }


        private Bestellung bestellung;

        [ManyToOne(0, Name = "Bestellung", Column = "BestellungId")]
        public virtual Bestellung Bestellung
        {
            get { return bestellung; }
            set
            {
                bestellung = value;
                this.MarkAsModified();
            }
        }


    }
Für das Speichern des Objektgraphs wurden einige Erweiterungsmethoden für die Klasse Session eingerichtet. Attach führt je nach Status SaveOrUpdate oder Delete aus. AcceptChanges führt sämtliche Zustandsaufzeichnungen des übergebenen Sets wieder zurück. Dies ist nach dem Speichern von Objekte erforderlich. Somit wird u. a. verhindert, dass NHibernate versucht, eine zum Speichern oder Löschen markierte Entität erneut zu speichern bzw. zu löschen. AttachAll hängt ruft für alle Entitäten eines Sets Attach auf.
public static class SessionExtensions {
        public static void Attach(this ISession session, IEntity obj) {
           
            if (obj.State == EntityState.Modified)
            {
                session.SaveOrUpdate(obj);
            }
            else if (obj.State == EntityState.Deleted)
            {
                session.Delete(obj);
            }
        }

        public static void AcceptChanges<T>(this Iesi.Collections.Generic.ISet<T> set)
        {
            foreach (IEntity entity in set)
            {
                entity.State = EntityState.Unmodified;
            }
            
        }


        public static void AttachAll<T>(this ISession session, Iesi.Collections.Generic.ISet<T> set)
        {

            foreach (IEntity entity in set)
            {
                Attach(session, entity);
            }
        }   
    }
Das nachfolgende Listing zeigt, wie diese Erweiterungsmethoden einzusetzen sind. In der gezeigten Methode wird zunächst für die übergebene abgehängte Bestellung SaveOrUpdate aufgerufen; anschließend werden die Zustände der geänderten Bestellungen mit AttachAll an die Session übergeben. Am Ende werden mit MarkAsUnModified bzw. AcceptChanges die Zustände auf Unmodified zurückgesetzt.
class BestellungDAO
{

    […]

    /// <summary>
    /// Speichert Bestellung inkl. Positionen
    /// </summary>
    /// <param name="b"></param>
    public void Save(Bestellung b)
    {
        using (ISession session = HibernateHelper.SessionFactory.OpenSession())
        {
            using (ITransaction trans = session.BeginTransaction())
            {
                session.SaveOrUpdate(b); // Keine Cascade !!
                session.AttachAll(b.Positionen);

                trans.Commit();

                b.MarkAsUnModified();
                b.Positionen.AcceptChanges();
            }
        }
    }
}
Wird nun die nachfolgend gezeigte Methode ausgeführt, bleiben am Ende die Positionen 1 und 3 übrig, wobei bei Position 3 die Anzahl auf 4 erhöht wurde. Das schöne dabei ist, dass lediglich die geänderten Positionen vom zweiten Aufruf von Save an die Session übergeben wird.
public static void SaveBestellung()
{
    BestellungDAO dao;
    dao = new BestellungDAO();

    Bestellung b = new Bestellung
    {
        BestellDatum = DateTime.Now,
    };

    Position p1 = new Position { Anzahl = 2, Pizza = "Test 1"};
    b.AddPosition(p1);

    Position p2 = new Position { Anzahl = 2, Pizza = "Test 2"};
    b.AddPosition(p2);

    Position p3 = new Position { Anzahl = 2, Pizza = "Test 3" };
    b.AddPosition(p3);

    dao.Save(b);

    p2.Anzahl = 4;
    p3.State = EntityState.Deleted;

    dao.Save(b);

}
Weitere Möglichkeiten:
  • Durch die Verwendung von Reflection und einer Tiefen- oder Breitensuche könnten alle geänderten Entitäten eines Objektgraphen mit nur einem Methodenaufruf an die Session übergeben werden.
  • Diese Implementierung  funktioniert lediglich für ISet aus den IesiCollections. Man sollte hier über eine allgemeine Implementierung nachdenken.

Kategorien: NHibernate