WP8 – SQL Ce – Linq2Sql – CodeFirst Approach – Relationen abbilden (1:n)

Hallo Leute,

quasi als Fortsetzung zu meinem letzten Blogeintrag (WP8 – SQL Ce – Linq2Sql) möchte ich euch zeigen, wie Ihr eine 1:n Relation abbilden könnt. Das ist leider gar nicht so einfach, vor allem wenn man auch DataBindings unterstützen möchte.

Für unser Beispiel nehmen wir einfach zwei Tabellen an: Mitarbeiter (n) und Abteilung (1).

Mitarbeiter
* Id (int) – PK
* Nachname (nvarchar(255))
* AbteilungId (int) – Fremdschlüssel

Abteilung
* Id (int) – PK
* Name (nvarchar(255))

Als ersten Schritt muss man nun der N-Seite eine sog. EntityReference hinzufügen. Das nachfolgende Codestück zeigt die Klasse Mitarbeiter, mit den drei Columns/Properties, angereichert mit den notwenigen Erweiterungen für die EntityReference:

[Table(Name = "Mitarbeiter")]
    public class Mitarbeiter : Base
    {
        public Mitarbeiter()
        {
		    // EntityReference
            this._Abteilung = default(EntityRef<Abteilung>);            
        }

        #region identity column for all tables
        private int _id = -1;
        [Column(Storage = "Id", DbType = "int IDENTITY(1,1) NOT NULL", IsPrimaryKey = true, IsDbGenerated = true)]
        public int Id
        {
            get
            {
                return _id;
            }
            set
            {
                if (value != _id)
                {
                    NotifyPropertyChanging();
                    _id = value;
                    NotifyPropertyChanged("Id");
                }
            }
        }
        #endregion

        
        private string _Nachname;
        [Column(Storage = "Nachname", DbType = "NVarChar(255) NOT NULL")]
        public string Nachname
        {
            get
            {
                return _Nachname;
            }
            set
            {
                if (value != _Nachname)
                {
                    _Nachname = value;
                    NotifyPropertyChanged("Nachname");
                }
            }
        }

        

        private int _AbteilungId;
        [Column(Storage = "AbteilungId", DbType = "int DEFAULT NULL")]
        public int AbteilungId
        {
            get
            {
                return _AbteilungId;
            }
            set
            {
                if (value != _AbteilungId)
                {
                    if (this._Abteilung.HasLoadedOrAssignedValue)
                    {
                        throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
                    }
                   
                    NotifyPropertyChanging();
                    _AbteilungId = value;
                    NotifyPropertyChanged("AbteilungId");
                }
            }
        }

        private EntityRef<Abteilung> _Abteilung;
        [Association(Name = "Mitarbeiter_Abteilung", Storage = "_Abteilung", ThisKey = "AbteilungId", OtherKey = "Id", IsForeignKey = true)]
        public Abteilung Abteilung
        {
            get
            {
                return this._Abteilung.Entity;
            }
            set
            {
                //.. TODO
            }
        }      

    }

Beachtet dabei, dass man im Konstruktor der Klasse die Variable _Abteilung instanzieren muss! Weiter unten, beim Property „Abteilung“, muss ein Association-Attribut hinzugefügt werden. Wir sind auf der N-Seite, bedeutet, dass ein Mitarbeiter nur EINE Abteilung besitzen kann, deshalb werden hier keine Collections oder dgl. verwendet.

Betrachtet man das Association-Attribut genauer, gibt es folgendes zu wissen:

[Association(Name = "Mitarbeiter_Abteilung", Storage = "_Abteilung", ThisKey = "AbteilungId", OtherKey = "Id", IsForeignKey = true)]

* Storeage… ist keine Spalte, welche in einer Tabelle verwendet wird, ist viel mehr für das Databinding notwendig
* ThisKey… Wir sind auf der N-Seite, es ist also der Foreign-Key anzugeben
* OtherKey… Primärschlüssel der 1-Seite
* IsForeignKey… Selbstredend…

Nun wollen wir den Setter des Properties fertigstellen:

private EntityRef<Abteilung> _Abteilung;
[Association(Name = "Mitarbeiter_Abteilung", Storage = "_Abteilung", ThisKey = "AbteilungId", OtherKey = "Id", IsForeignKey = true)]
public Abteilung Abteilung
{
	get
	{
		return this._Abteilung.Entity;
	}
	set
	{
		Abteilung previousValue = this._Abteilung.Entity;
		if (((previousValue != value) || (this._Abteilung.HasLoadedOrAssignedValue == false)))
		{
			NotifyPropertyChanging();
			if ((previousValue != null))
			{
				this._Abteilung.Entity = null;
				previousValue.Mitarbeiter.Remove(this);
			}
			this._Abteilung.Entity = value;
			if ((value != null))
			{
				value.Mitarbeiter.Add(this);
				this._AbteilungId = value.Id;
			}
			else
			{
				this._AbteilungId = -1;
			}
			NotifyPropertyChanged("Abteilung");
		}
	}
}

Um das Property „Abteilung“ später beim Databinding verwenden zu können, ist der Aufruf der Methoden NotifyPropertyChanged() und NotifyPropertyChanging() empfehlenswert (in meinem letzten Blogeintrag habe ich euch gezeigt, dass meine Base-Klasse von den Interfaces INotifyPropertyChanged und INotifyPropertyChanging ableitet).

Zudem seht Ihr, dass der ForeignKey (das Property „this._AbteilungId“) entsprechend gesetzt wird. Der Rest des Codes wird klar, sobald wir uns die 1-Seite, also die Tabelle Abteilung, angesehen haben.

Der Klasse Abteilung muss das Gegenstück zur EntityReference, nämlich das sog. EntitySet, hinzugefügt werden. Das nachfolgende Codebeispiel zeigt die Klasse Abteilung mit der Erweiterung.

[Table(Name = "ORG_ABTEILUNG")]
public class Abteilung : Model.Base
{
	public Abteilung()
	{
		this._mitarbeiter = new EntitySet<Mitarbeiter>(new Action<Mitarbeiter>(this.attach_Mitarbeiter), new Action<Mitarbeiter>(this.detach_Mitarbeiter));
	}

	private int _id = -1;
	[Column(Storage = "Id", DbType = "int IDENTITY(1,1) NOT NULL", IsPrimaryKey = true, IsDbGenerated = true)]
	public int Id
	{
		get
		{
			return _id;
		}
		set
		{
			if (value != _id)
			{
				NotifyPropertyChanging();
				_id = value;
				NotifyPropertyChanged("Id");
			}
		}
	}
	
	private string _Name;
	[Column(Storage = "Name", DbType = "NVarChar(255) NOT NULL")]
	public string Name
	{
		get
		{
			return _Name;
		}
		set
		{
			if (value != _Name)
			{
				NotifyPropertyChanging();
				_Name = value;
				NotifyPropertyChanged("Name");
			}
		}
	}

   
	#region Reference Mitarbeiter n:1 Abteilung
	private EntitySet<Mitarbeiter> _mitarbeiter;
	[Association(Name = "Mitarbeiter_Abteilung", Storage = "_mitarbeiter", ThisKey = "Id", OtherKey = "AbteilungId", DeleteRule = "NO ACTION")]
	public EntitySet<Mitarbeiter> Mitarbeiter
	{
		get
		{
			return this._mitarbeiter;
		}
		set
		{
			this._mitarbeiter.Assign(value);
		}
	}

	private void attach_Mitarbeiter(Mitarbeiter entity)
	{
		NotifyPropertyChanging();
		entity.Abteilung = this;
	}

	private void detach_Mitarbeiter(Mitarbeiter entity)
	{
		NotifyPropertyChanging();
		entity.Abteilung = null;
	}
	#endregion
}

Wir Ihr seht, wird im Konstruktur die Variable _mitarbeiter instanziert. Dabei wird dieser Variable ein neues EntitySet zugewiesen, welches die Actions „onAdd“ (attach_Mitarbeiter) und „onRemove“ (detach_Mitarbeiter) setzt. Das Property „Mitarbeiter“ ist klarerweise eine Collection (EntitySet), da eine Abteilung ja mehreren Mitarbeitern zugewiesen werden kann.

Betrachtet man jetzt nochmal das Property Abteilung der Klasse Mitarbeiter genauer, sieht man, dass auf das eben erzeugte Property Mitarbeiter der Klasse abteilung zugegriffen wird (value.Mitarbeiter.Add bzw. previousValue.Mitarbeiter.Remove). Dieses Add und Remove triggert die beiden Actions, welche wir vorhin definiert haben (detach_Mitarbeiter, attach_Mitarbeiter).

Somit ist sichergestellt, dass beide Seiten bei einer Veränderung diese Änderung auch mitbekommen und das (in weiterer Folge mögliche) Databinding auch immer noch korrekt ist.

Cheers,
Christian

Leave a reply