Laden...

Schichtentrennung am Beispiel Entity Framework Core

Erstellt von BlackMatrix vor 7 Jahren Letzter Beitrag vor 7 Jahren 2.526 Views
B
BlackMatrix Themenstarter:in
218 Beiträge seit 2012
vor 7 Jahren
Schichtentrennung am Beispiel Entity Framework Core

Hallo Community,

ich habe mal wieder eine Frage zu der Schichtentrennung mithilfe des Entity Frameworks. Ich habe mich nun dazu entschlossen das Entity Framework Core zu verwenden, aber meine Frage ist eher allgemeiner Natur. Sicher wurde auch die ein oder andere Frage auch schon mal hier im Forum beantwortet, aber so richtig zufriedenstellend war das für mich leider noch nicht.

Ich versuche gerade meine Anwendung sauber aufzutrennen. Korrigiert mich, wenn ich irgendetwas falsch mache.
Ich habe eine Datenschicht, die das EF benutzt. Für jede Tabelle, die später in der Datenbank erstellt werden soll, habe ich eine Entität angelegt. Das heißt auch, dass ich dabei auf Normalisierung geachtet habe. Zum Beispiel habe ich zwar eine "Personen"-Tabelle und damit auch eine "Person"-Entität, aber da z.B. der Vorname einer Person in eine eigene Tabelle soll um Redundanzen in der DB zu vermeiden, gibt in der PersonEntity auch eine FirstName-Property vom Type FirstNameEntity. Gleiches gilt für weitere Daten der Person, die ich gerne extra in Tabellen auslagern möchte. Vorname, Nachname, Adresse, Username, Mailadresse, Useragent, OS, ...

Dann habe ich eine über der Datenschicht liegende Schicht. Nennen wir sie Geschäftslogik. Diese ist dafür zuständig, dass Personen zu richtigen Person-Objekten werden. D.H. auch wenn in der Datenschicht der Vorname bswp. in einer eigenen Tabelle ausgelagert ist, hat die richtige "CLR-Person" nur einen String als Vornamen und hat auch keine Navigationproperties mehr, die in der Datenschicht zum Einsatz kommen. In dieser Schicht wird auch die Funktionalität bereitgestellt bswp. CLR-Personen hinzuzufügen.

Die letzte Schicht würde ich als ViewModel-Schicht bezeichnen. Der Nutzer legt bspw. eine neue Person in der Oberfläche an, das ViewModel validiert die Eingaben und erzeugt letztendlich ein CLR-Personobjekt.

Nun wird die CLR-Person an die Geschäftslogik weitergereicht:


        public interface IPersonService
        {
             Task AddPersonsAsync(IEnumerable<Person> persons);
        }
        public class PersonService
        {
             public async Task AddPersonsAsync(IEnumerable<Person> persons)
             {
                // ...
             }
        }

Jedenfalls scheint es mir nun ein Krampf zu sein, die CLR-Person(en) in die Datenbank einzutüten. Und ich habe das Gefühl, dass ich mir damit all die Vorzüge vom EF verbaue, weil ich die Entitäten nicht direkt in meinen darüberliegenden Schichten verwende.

  using (var dataContext = new DataContext())
            {
                var alreadyTrackedLastNames = new List<LastNameEntity>();
                var alreadyTrackedFirstNames = new List<FirstNameEntity>();
                var alreadyTrackedOperatingSystems = new List<OperatingSystemEntity>();
                
                foreach (var person in persons)
                {
                    var personEntity = person.ToEntity();

                    // Firstname
                    var foundFirstName = alreadyTrackedFirstNames.FirstOrDefault(x => x.Gender == personEntity.FirstName.Gender && x.FirstName == personEntity.FirstName.FirstName);
                    if (foundFirstName == null)
                    {
                        foundFirstName = await dataContext.FirstNames.SingleOrDefaultAsync(x => x.Gender == personEntity.FirstName.Gender && x.FirstName == personEntity.FirstName.FirstName);
                    }
                    if (foundFirstName == null)
                    {
                        alreadyTrackedFirstNames.Add(personEntity.FirstName);
                    }
                    else
                    {
                        personEntity.FirstName = foundFirstName;
                    }

                    // Lastname
                    var foundLastName = alreadyTrackedLastNames.FirstOrDefault(x => x.LastName == personEntity.LastName.LastName);
                    if (foundLastName == null)
                    {
                        foundLastName = await dataContext.LastNames.SingleOrDefaultAsync(x => x.LastName == personEntity.LastName.LastName);
                    }
                    if (foundLastName == null)
                    {
                        alreadyTrackedLastNames.Add(personEntity.LastName);
                    }
                    else
                    {
                        personEntity.LastName = foundLastName;
                    }                   

                    // OperatingSystem
                    var foundOperatingSystem = alreadyTrackedOperatingSystems.FirstOrDefault(x => x.Value == personEntity.OperatingSystem.Value);
                    if (foundOperatingSystem == null)
                    {
                        foundOperatingSystem = await dataContext.OperatingSystems.SingleOrDefaultAsync(x => x.Value == personEntity.OperatingSystem.Value);
                    }
                    if (foundOperatingSystem == null)
                    {
                        alreadyTrackedOperatingSystems.Add(personEntity.OperatingSystem);
                    }
                    else
                    {
                        personEntity.OperatingSystem = foundOperatingSystem;
                    }

                    // Address
                    var foundAddress = await dataContext.Addresses.SingleOrDefaultAsync(x => string.Equals(x.Street, person.Address.Street, StringComparison.OrdinalIgnoreCase) &&
                                                                                             string.Equals(x.HouseNumber, person.Address.HouseNumber, StringComparison.OrdinalIgnoreCase) &&
                                                                                             string.Equals(x.ZipCode, person.Address.ZipCode, StringComparison.OrdinalIgnoreCase) &&
                                                                                             string.Equals(x.City, person.Address.City, StringComparison.OrdinalIgnoreCase) &&
                                                                                             string.Equals(x.CountryCode, person.Address.CountryCode, StringComparison.OrdinalIgnoreCase));
                    if (foundAddress != null)
                    {
                        personEntity.Address = foundAddress;
                        personEntity.Address.Person = personEntity;
                    }

                    // ...

                    dataContext.Persons.Add(personEntity);
                }

                await dataContext.SaveChangesAsync();

Muss das so kompliziert sein? Irgendwie sehe ich nicht mehr so richtig durch und das ist nur ein Objekt von vielen das ich in die Datenbank überführen muss...

Viele Grüße

BlackMatrix

W
955 Beiträge seit 2010
vor 7 Jahren

Hallo,

* beschäftige dich mal mi dem generischen Repository-Pattern
* mit Tools wie Automapper kannst du zwischen Modell- und Persistenzobjekt vermitteln
* ich würde nicht eine so extreme DB-NF erzwingen. Es gibt keine Gemeinsamkeiten / Vorteile dass zwei Personen die Matthias heissen zusammengefasst werden sollten. Verwende eine passende NF die gut zwischen Performanze und Persistenz liegt.

16.842 Beiträge seit 2008
vor 7 Jahren

Hinweis. AutoMapper niemals mit DbContext mischen. Ist eine ausdrückliche Empfehlung.
Der AutoMapper ist weniger dazu gedacht, dass er Entitäten mappt, viel mehr ViewModels (zu Business Models).

Namen, Geburtstage und Adressen etc exkludiert man i.d.R. von DB-NF.

2.207 Beiträge seit 2011
vor 7 Jahren

Hallo BlackMatrix,

vielleicht hilft dir ASPNET-Core-Entity-Framework-Core auch zum Einstieg.

Muss noch ein bisschen was fixen sehe ich grad, mache ich daheim 😉

Aber als Anhaltspunkt kann es schonmal helfen.

Gruss

Coffeebean