Laden...

EF6, CodeFirst - In Entity-Klasse eigene Abfrage abschicken

Erstellt von Palladin007 vor 7 Jahren Letzter Beitrag vor 7 Jahren 5.471 Views
Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren
EF6, CodeFirst - In Entity-Klasse eigene Abfrage abschicken

Guten Nachmittag,

vorweg: Das Thema habe ich gestern auch hier geöffnet, bisher aber keine Antwort bekommen.

ich hänge gerade an einem kleinen Problem mit EF6 und CodeFirst
Und zwar möchte ich Listen in der Datenbank mit verschiedenen Eigenschaften speichern.
Davon gibt es aber verschiedene Listen, weshalb ich eine Basisklasse habe und für die verschiedenen Varianten entsprechende ableitende Klassen.
Soweit so gut, mit Vererbung kommt EF6 ja klar.

Aktuell habe ich drei verschiedene Listen:

  • Die Inhalte werden von dem Benutzer manuell editiert
  • Die Inhalte werden durch vordefinierte Abfragen basierend auf eigenen Abfragen abgerufen
  • Die Inhalte werden durch vom User definierte Abfragen abgerufen

Die erste Liste ist egal, das regelt EF6 automatisch.
Für die anderen beiden Listen brauche ich aber die Möglichkeit, in der Klasse selber Abfragen abzuschicken und die Ergebnisse zu verarbeiten.
Ich brauche dort also Zugriff auf meinem DbContext, aber die bekomme ich den?

Ich möchte ungern den Context irgendwo statisch vor halten.
Ich möchte den eigentlich in einem eigenen Objekt gekapselt verwalten, das dann je nach Bedarf die Verbindung öffnet/schließt und dieses Objekt wird über einen IoC-Container verfügbar sein.
Ich möchte auch nicht die Query dort ausführen, wo ich sie nutze, da das mehrere Stellen sind.

Hat da jemand eine Idee, wie ich den DBContext bekomme oder wie ich mein Vorhaben anders erreichen kann?

Meine aktuelle "Lösung" sieht jetzt so aus:

public class DbContextLocator
{
    public static DbContextLocator Instance { get; } = new DbContextLocator();

    private readonly ICollection<DbContext> _registeredContexts;
    private readonly IDictionary<object, DbContext> _entityContexts;
    private readonly IDictionary<DbSet, NotifyCollectionChangedEventHandler> _dbSetLocalChangedHandler;

    private DbContextLocator()
    {
        _registeredContexts = new List<DbContext>();
        _entityContexts = new ConcurrentDictionary<object, DbContext>();
        _dbSetLocalChangedHandler = new ConcurrentDictionary<DbSet, NotifyCollectionChangedEventHandler>();
    }

    public TDbContext FindDbContext<TDbContext>(object entity)
        where TDbContext : DbContext
    {
        return FindDbContext(entity) as TDbContext;
    }
    public DbContext FindDbContext(object entity)
    {
        return _entityContexts.ContainsKey(entity) ? _entityContexts[entity] : null;
    }

    public void RegisterContext(DbContext context)
    {
        foreach (var dbSet in GetDbSets(context))
            AddDbSetLocalChangedHandler(dbSet, context);

        lock (_registeredContexts)
        {
            if (!_registeredContexts.Contains(context))
                _registeredContexts.Add(context);
        }
    }
    public void DeregisterContext(DbContext context)
    {
        foreach (var dbSet in GetDbSets(context))
        {
            var observableCollection = dbSet.Local as INotifyCollectionChanged;

            if (_dbSetLocalChangedHandler.ContainsKey(dbSet))
                observableCollection.CollectionChanged -= _dbSetLocalChangedHandler[dbSet];

            foreach (var entity in dbSet)
                _entityContexts.Remove(entity);
        }

        lock (_registeredContexts)
            _registeredContexts.Remove(context);
    }

    private void AddDbSetLocalChangedHandler(DbSet dbSet, DbContext context)
    {
        var observableCollection = dbSet.Local as INotifyCollectionChanged;

        if (!_dbSetLocalChangedHandler.ContainsKey(dbSet))
        {
            _dbSetLocalChangedHandler.Add(dbSet, (sender, e) =>
            {
                foreach (var oldEntity in e.OldItems?.Cast<object>() ?? new object[0])
                    _entityContexts.Remove(oldEntity);

                foreach (var newEntity in e.NewItems?.Cast<object>() ?? new object[0])
                {
                    if (!_entityContexts.ContainsKey(newEntity))
                        _entityContexts.Add(newEntity, context);
                }
            });

            observableCollection.CollectionChanged += _dbSetLocalChangedHandler[dbSet];
        }
    }
    private static IEnumerable<DbSet> GetDbSets(DbContext context)
    {
        var objectContext = ((IObjectContextAdapter)context).ObjectContext;
        var container = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);

        return from entitySet in container.EntitySets
                let entityType = Type.GetType(entitySet.ElementType.FullName)
                select context.Set(entityType);
    }
}

Was ich im Prinzip mache, ist folgendes:
Vom DbContext alle DbSets abfragen und für jedes DbSet auf die Local-Property ein CollectionChanged-Handler registrieren.
So kann ich für jedes hinzugefügte oder entfernte Entity-Objekt den aktuellen DbContext im Dictionary bereit halten.
In jeder Entity, die das braucht, muss ich dann noch einen Konstruktor-Parameter für den DbContext hinzufügen, damit ich den beim neu erstellen einer Entity mit geben kann.
Damit EF damit arbeiten kann, brauchts aber noch einen parameterlosen Konstruktor, aber der kann auch private sein.

Das funktioniert, zumindest soweit ich das getestet habe, aber es wirkt für mich wie eine ziemlich wackelige Krücke.
Wenn jemand eine bessere Idee hat, wäre ich da sehr dankbar.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.830 Beiträge seit 2008
vor 7 Jahren

Ich decke eine solche Logik mit dem Business Layer / Services ab, die entsprechende Methoden bereit stellen.
Für mich hört sich das hier irgendwie - vllt versteh ich es nicht ganz - nach einer sehr umständlichen, fehleranfälligen und quasi nicht testbaren Konzeption an. Viel Magic. Nicht? =)

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

Ich wollte ursprünglich auch ein eigenes Business-Layer umsetzen.
Ich habe mich wegen dem damit verbundenen ziemlich großen Aufwand dafür entschieden, BusinessLayer und DataLayer zusammen zu fassen - mit dem CodeFirst-Ansatz ist das ja ziemlich gut möglich. Außerdem unterstützt das EF so ziemlich alles was ich brauche. Nur das Mocken finde ich etwas umständlich, aber das lässt sich auch machen.

Oder kennst Du ein gutes testbares BusinessObject-Framework?
Ich kenne nur CSLA, hab damit in der Ausbildung gearbeitet und bin nicht wirklich ein Freund davon.
EIn BusinessLayer wäre mir schon lieber

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.830 Beiträge seit 2008
vor 7 Jahren

Man mockt ja auch nicht das EF, sondern man mockt zB Repositories und Services 😉

Diese Abfragen.. wo werden diese erstellt und warum brauchen diese den DbContext?
Warum deckst Du das nicht in der Logikschicht ab?

Eigentlich gestatten wir Crossposts erst, wenn ein paar Tage und nicht erst ein paar Stunden niemand geantwortet hat 😉

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

Man mockt ja auch nicht das EF, sondern man mockt zB Repositories und Services 😉

Also das EF wiederum hinter einem Repository verstecken?
Ja, wollte ich auch erst machen, damit habe ich aber eine weitere Schicht dazwischen, die im Prinzip nur das Layout meiner Datenschicht kopiert.

An sich würde ich das gerne so machen. Gegen das BO-Layer entwickeln, wo verschiedene Dinge zu finden sind und der Zugriff auf die Datenschicht geregelt wird.
Allein der Aufwand ist dabei auch gar nicht so sehr das Problem, sondern dass ich damit viele verschiedene Typen habe, die ich irgendwie kapseln muss. Das BO arbeitet intern mit dem Entity-Interface, das wird von der POCO-Klasse implementiert. Sobald ich Fremdschlüssel habe, muss ich ständig Properties casten und bei den Collections muss ich das ebenfalls tun.

Da hab ich mir die Frage gestellt, ob ich das überhaupt brauche und bin zu dem Schluss gekommen: Eigentlich nicht - scheinbar habe ich mich da geirrt.

Diese Abfragen.. wo werden diese erstellt und warum brauchen diese den DbContext?

Die eine Abfrage basiert auf einer Property in der abgeleiteten Liste. Die Abfrage ist nicht kompliziert, ein simpler Check auf eine ID.
Die zweite Abfrage wird vom Benutzer definiert. Dafür habe ich mir das Specification-Pattern heraus gesucht. Eine solche Specification kennt die Abfrage als Expression und kann sie auch als Methode ausführen.
Diese Specifications kann ich seperat testen, das Umsetzen in die Abfrage gegen die Datenbank sollte mMn Aufgabe der Repository-Implementierung sein.

Warum deckst Du das nicht in der Logikschicht ab?

Die Logikschicht - so ganz ist mir nicht klar, wie genau die aussehen sollte.
Ich kenne das so, dass die Logikschicht hauptsächlich aus den verschiedenen Features besteht, die möglichst unabhängig voneinander sind und die dann entweder einzeln aufgerufen oder über das BusinessLayer verfügbar gemacht werden.

Im Prinzip würde ich das daher dann in das BusinessObject ablegen - Also das, was Du schon in deiner ersten Antwort gesagt hast.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

5.658 Beiträge seit 2006
vor 7 Jahren

Hi Palladin007,

bezüglich der Begrifflichkeiten und Zuständigkeiten der Schichten hilft dir evtl. dieser Artikel weiter: [Artikel] Drei-Schichten-Architektur.

Eine benutzerdefinierte Abfrage ist Teil der Anwendungslogik und gehört daher in den Business-Layer.

Wenn du dir bspw. von einer Repository-Methode ein IQueryable-Objekt zurückgeben läßt, kannst du deine Abfragen einfach im BL darauf anwenden, z.B.:


IList<Product> filteredProducts = repository.GetProducts()
  .Select(...)
  .OrderBy(...)
  .ApplyCustomFilters() // Hier werden deine Benutzerdefinierten Filter angewandt
  .ToList(); // Hier wird erst die Datenbankabfrage durchgeführt

ApplyCustomFilters ist hier eine Erweiterungsmethode für IQueryable<Product>, die den benutzerdefinierten Filter anwendet. Das geht natürlich auch ohne Erweiterungsmethoden.

Weeks of programming can save you hours of planning

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

In Summe klingt das so, wie wenn ich um ein eigenes BusinessLayer nicht drum herum komme?

Das wollte ich ja ursprünglich vermeiden, aber dann krame ich wohl lieber mal mein Backup, wo ich das schon begonnen habe, raus 😄

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

5.658 Beiträge seit 2006
vor 7 Jahren

Also ich kann deine Entscheidung, auf den BL zu verzichten, jedenfalls nicht nachvollziehen. Du sagst ja auch selbst über deine Lösung:

es wirkt für mich wie eine ziemlich wackelige Krücke.

Du mußt es ja nicht so machen, wie vorgeschlagen. Aber schließlich hattest du nach besseren Vorschlägen gefragt.

Weeks of programming can save you hours of planning

16.830 Beiträge seit 2008
vor 7 Jahren

Bei mir sieht das so aus:

  • Applikation
  • Business Layer (auch genannt Business Services, i.d.R. via Repository Pattern)
  • DAL (Repository Pattern)
  • ADO.NET (zB Entity Framework)

Repositories im DAL kennen eigentlich nur CRUD-Operationen, oder Operationen bei der Datenbank-Logik notwendig ist.
Meine Services regeln dann die Business Logik, zB. stellen Abfrageresultate der Daten nach außen zur Verfügung.

zB habe ich nur einen UserService, aber mehrere Repositories der User, zB UserDetailsRepository, UserRepository, UserRoleRepository...(pro Entität ein Repository).
Am Ende hab ich nach außen quasi ein BusinessModel "User" aber mehrere Entitäten, zB UserEntity, UserRoleEntity, UserDetails.. etc etc...
Ist etwas mehr initialer Aufwand aber vieles vieles geht sehr viel einfacher.

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

Kennst Du dafür irgendein Framework, was die Arbeit vereinfacht?
Besonders im BusinessLayer leite ich ja nur an das jeweilige Entity-Objekt weiter

Oder muss/sollte ich das per Hand machen?

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.830 Beiträge seit 2008
vor 7 Jahren

Ich wüsste jetzt kein Framework, das mir bei sowas helfen könnte.
Für das Mappen von Entitäten / Abfrageresultate auf Business Objekte und Business-Objekte auf ViewModels verwende ich i.d.R. meinen FlexMapper.

Der Business Layer tut viel mehr als nur weiterleiten.
Darin steckt die Logik Deiner Anwendung.

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

Ok, dann mach ich es selber

Ich danke für die Hilfe 😃

Eine kurze Frage hab ich aber noch:
Was haltet ihr davon, vom Repository ein IQueryable zu bekommen und zu verwenden?
Das hebelt im Prinzip ja die Schichtentrennung auf, da die eigentliche Abfrage erst in den anderen Schichten ausgeführt wird.
Außerdem, angenommen ich tausche die Repository-Implementierung aus, dann bin ich drauf angewiesen, dass die neue Implementierung das auch unterstützt - oder ich setze es selber um.

Oder sollte ich statdessen für alle Abfragen, die ich brauche, im Repository oder BusinessLayer entsprechende Methoden bereit stellen?

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.830 Beiträge seit 2008
vor 7 Jahren

Ein Repository darf nichts anderes zurückgeben als IEnumerable oder IQueryable, außer es sind Skip-Take Methoden.
Das Repository kann gar nicht wissen, wann welche Daten geladen werden müssen. Deswegen muss die Materialisierung (also ToList) im Business Layer erfolgen und nicht im Repository.

Beispiel: Es kann praktisch sein, dass GetAll wirklich alles zurück gibt, weil dann alles mit einem Select geladen wird.
Bei vielen Daten wird es aber dazu führen, dass es eine OutOfMemoryException gibt. Dann darf ToList nicht verwendet werden, sondern man lädt es einzeln oder in Chunks.
Diese Logik muss im BL liegen.

Das ist auch keine Schichtenverletzung sondern das Gegenteil 😉

Meine Reps haben eigentlich nur CRUD Methoden; im Einzelfall bestimmte Abfragen.
Aber keine logischen Queries. Das ist BL.

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

Ok, dann werde ich das auch in's BusinessLayer legen.
Eigentlich brauche ich dann ja auch Methoden wie FindById nicht mehr, die umgesetzten Queries sind zumindest beim EF6 ja recht gut - zumindest habe ich bisher kein wirkliches Negativ-Beispiel gesehen ^^

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.830 Beiträge seit 2008
vor 7 Jahren

Hier noch ne Hilfestellung, was ich derzeit für das Entity Framework 6 als Basis Repository verwende.

    public abstract class BaseRepository<TEntity> where TEntity : class, IEntity
    {
        public StringComparison ComparisonCulture = StringComparison.InvariantCultureIgnoreCase;

        public IDbContext DbContext { get; }

        protected BaseRepository(IDbContext dbContext)
        {
            DbContext = dbContext;
        }
        
        public DbSet<TEntity> CurrentSet => DbContext.Set<TEntity>();

        // CRUD
        public virtual TEntity Get(Guid id)
        {
            return Get(entity => entity.Id == id);
        }
        public virtual TEntity Get(Func<TEntity, bool> clause)
        {
            return CurrentSet.SingleOrDefault(clause);
        }
        public virtual IQueryable<TEntity> GetAll()
        {
            return CurrentSet.AsQueryable();
        }
        public virtual IQueryable<TEntity> GetMany(Func<TEntity, bool> clause)
        {
            return CurrentSet.Where(clause).AsQueryable();
        }

        public virtual TEntity Add(TEntity entity)
        {
            return CurrentSet.Add(entity);
        }

        public virtual TEntity Update(TEntity entity)
        {
            return CurrentSet.Attach(entity);
        }

        public virtual TEntity Delete(TEntity entity)
        {
            return CurrentSet.Remove(entity);
        }

        // Save
        // Count
        ...
    }

Hierarchie sieht aber bei mir dann etwas so aus:

  • BaseRepository
  • TrackedEntityRepository (setzt Werte wie LastUpdatedOn, das jedes meiner Entitäten hat, bietet Events an, zB EntityAdded, EntityUpdated etc.)
  • ValidatableEntityRepository (ich verwende oft nicht die EF6 Context Validation, weil es zu langsam ist. Hier ist eine Validierung auf Basis von DataAnnotations und IValidatableObject)
  • <Entity>Repository wie UserRepository. Sind aber oft leer bzw. haben nur das Inteface für IoC.
Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

So ungefähr hab ich das jetzt auch, allerdings habe ich die CRUD-Methoden (außer Create) auf IEnumerable<TEntity> ausgelegt. Ich finde, so lasse ich der Implementierung mehr Freiheiten.
Für Aufrufe mit einer Entity habe ich dann passende Erweiterungsmethoden, die ein Array mit der Entity erzeugen und weiter reichen.
Was ich aber nicht verstehe: Wozu das ComparisonCulture-Feld?

Du hast ein TrackedEntityRepository und ein ValidatableEntityRepository, würde das im BusinessLayer nicht besser passen?
Mein Gedanke dahinter war, dass das Repository (und UnitOfWork) wirklich nur der reine Datenzugriff ist und die einzige Logik, die es hat, ist die Verwaltung der Connection und die Grundaufgaben, die ein ORM eben erfüllen muss.

Lagere ich das in das BusinessLayer aus, dann kann ich dort ein Validierungs-System umsetzen, das ich dann überall nutze, wo ich es brauche. Neben der UI ist eine Import-Funktion ein ganz gutes Beispiel, da die Daten dort ja auch validiert werden müssen.
So bekommt die Anwendung nichts von dem Repository mit, es kennt nur das BusinessLayer, das das Repository intern kapselt.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.830 Beiträge seit 2008
vor 7 Jahren

Bezüglich IEnumerable vs IQueryable les Dir mal folgendes durch , ob das einen Impact auf Dich hat: In the spotlight: Demystifying IQueryable (Entity Framework 6).
Das EF verhält sich nämlich etwas anders; es ist bei IQueryable effizienter.

Du hast ein TrackedEntityRepository und ein ValidatableEntityRepository, würde das im BusinessLayer nicht besser passen?

Nein, in diesem Fall nicht. Hier werden nur Datenbankfelder überprüft, zB. ob das E-Mail-Feld nicht leer ist - keine logischen Abhängigkeiten. Es ist die Aufgabe des Repositories, dass die Entitäten in einen gültigen und aus DB-Sicht ("Feld nicht leer") in korrekter Form in der DB landen.

In meinem Service prüfe ich, ob die E-Mail eine gültige Form ("sieht aus wie eine E-Mail") hat, ob sie nicht auf einer Blacklist ist, ob die Domain nicht auf einer Blacklist ist und ob sie bereits verwendet ist.
Wobei meine Modelle ebenfalls IValidableObject implementieren und dort zB dann geprüft wird: Ja, die E-Mail hat eine korrekte Form (Regex) aber die Prüfung mit externen Quellen wie einer DB passiert im Service.

Meine Services haben auch einen ähnlichen Aufbau:

  • BaseService
  • SelfValidatableService
  • <Model>Service, zB UserService.

Wie gesagt; der Impact von meinem Aufbau ist nicht gering (Overhead so 250 Zeilen).
Aber den mach ich ein einziges Mal und kann ihn überall wiederverwenden 😃
Du kannst das theoretisch auch alles im Service validieren, wenn Du Dir zB. die Flexibilität lassen willst, dass die Datenbank wenig eigene Regeln hat.
Wollte Dir auch weniger mein Zeug auf die Nase binden als Dir nur die Info zu geben, wie ich/wir das tun - und warum 😉

742 Beiträge seit 2005
vor 7 Jahren

Was soll eine Db Sicht sein? Für eine DB ist eher wichtig, dass der Primary Key nicht null sein darf, dass es nicht mehr als X Columns gibt usw. Alles andere sind Business Regeln, die (aus gutem Grund) auch in der Datenbank durchgesetzt werden.

Für mich persönlich funktioniert es übrigens besser, wenn ich richtige Business Objekte in das Repository packe. Da ist der Aufwand aber wirklich deutlich höher, weil diese nicht immer 1:1 auf die DB gemappt werden können.

16.830 Beiträge seit 2008
vor 7 Jahren

Steht doch da: zB dass ein Feld nicht leer ist.

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

Der Impact von meinem Aufbau wird auch nicht gerade gering sein 😄
Deshalb hab ich das Thema hier ja aufgemacht, ich möchte einen Aufbau finden, der auch in Zukunft noch gut funktioniert, schließlich ist das der Bereich, bei dem eine Änderung potentiell den größten Aufwand im Rest der Anwendung bedeutet.

Wollte Dir auch weniger mein Zeug auf die Nase binden als Dir nur die Info zu geben, wie ich/wir das tun - und warum 😉

Genau das will ich ja 😄
Allerdings denke ich auch darüber nach und schreibe auf, was ich dabei für Bedenken habe.

So wie ich das jetzt verstanden habe, hast Du Validierung sowohl im Repository, also für die reinen Entity-Typen, aber auch für das Business-/Service-Layer?
Im Repository wird dann sowas validiert, was sich auch auf der Datenbank widerspiegeln lässt, also z.B. die maximale Länge eines Strings und dass der nicht leer ist, während das Business-/Service-Layer die restlichen Dinge validiert, wie die Frage, ob der String eine valide Email-Adresse ist.

Klingt logisch. Tut das Repository das nicht, muss das Business-Layer das übernehmen und benötigt damit dann indirekt Existenz von der Implementierung des Repositories, schließlich muss es ja die Grenzen kennen, die auf der Datenbank gegeben sind.

Aber wie bilde ich die Validierung am besten ab?
IValidableObject hat denke ich das Problem, dass das ValidationResult am Ende nur ein String hat, über den ich den Fehler erkennen kann.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

742 Beiträge seit 2005
vor 7 Jahren

Wie sehen denn deine Business Objekte typischerweise aus? Mir ist das Setup nicht ganz klar. "Business Layer" kann ja alles mögliche bedeuten.

16.830 Beiträge seit 2008
vor 7 Jahren

Meinst Du mich Sebastian?

IValidableObject hat denke ich das Problem, dass das ValidationResult am Ende nur ein String hat, über den ich den Fehler erkennen kann.

    public abstract class ValidatableEntity : Entity
    {
        /// <summary>
        /// Validates and returns true if no error found
        /// </summary>
        public bool IsValid()
        {
            IEnumerable<ValidationResult> errors;
            return IsValid(out errors);
        }


        /// <summary>
        /// Return true if validation was successfully. <see cref="errors"/> is empty if valid.
        /// </summary>
        public bool IsValid(out IEnumerable<ValidationResult> errors)
        {
            errors = Validate();
            return !errors.Any();
        }


        /// <summary>
        /// Executes the validations with a new, empty validation context
        /// </summary>
        /// <returns>Empty list if no errors found</returns>
        public IEnumerable<ValidationResult> Validate()
        {
            try
            {
                var errors = Validate(new ValidationContext(this, null, null));
                return errors;
            }
            catch(Exception e)
            {
                // TODO: Handle here
                return new List<ValidationResult>();
            }
        }

        /// <summary>
        /// Executes the validations with the given validation context
        /// </summary>
        /// <returns>Empty list if no errors found</returns>
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            List<ValidationResult> results = new List<ValidationResult>();
            Validator.TryValidateObject(this, validationContext, results, true);
            return results;
        }
    }
Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

Ja klar, aber dann habe ich nach dem Validieren doch trotzdem noch nur einen String, über den ich den Fehler bestimmt kann. Gut, String stimmt nicht ganz, aber ein ValidationResult, was nur einen String beinhaltet - und die Member-Namen

Ich vermisse da die Möglichkeit, einen Fehler eindeutig zuordnen zu können, z.B. anhand eines Fehler-Codes.
Oder übertreibe ich es da? Theoretisch sollte ein solcher Fehler ja sowieso nicht entstehen, da in der UI jeder Fehleingabe abgefangen werden sollte.

Tut mir Leid, dass ich da so viel nach frage, ich will einfach keinen groben Fehler machen, der mich später viel Zeit kostet.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

742 Beiträge seit 2005
vor 7 Jahren

Nee, Palladin, sorry.

16.830 Beiträge seit 2008
vor 7 Jahren

Du kannst da ja auch was anderes zurück geben als ein ValidationResult. ValidationResult ist halt der Standard,

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

@malignate:

Das BusinessLayer würde bei mir das Repository kapseln.
Im Prinzip bereitet es die reine Datenschicht für das Programm auf.

Als Beispiel:
Ich hab eine sortierte Liste, die ich auf der Datenbank als m:n-Beziehung habe.
In der Zwischentabelle gibt es eine Spalte namens Index, anhand der ich die Position in der Liste definiere.
Im Repository habe ich diese Tabellen dann genau abgebildet, also z.B. die Interfaces IProduct, IProductList und IProsuctListProduct. Letztere hat dann den Index
Im BusinessObject für ProductList habe ich dann nur noch eine IList<Product>. Beim Speichern wird dann der Index entsprechend mit beachtet.

Dort würde ich auch die Validierung unter bringen, die nicht direkt für die Datenbank notwendig ist, z.B. Abts Beispiel, ob eine Email-Adresse valide ist und nicht auf einer Blacklist liegt.

Allgemein landen dort Features, die zu eng an das Data-Layer gebunden sind aber auch nicht ins Repository gehören, weil sie keine konkrete Kenntnis der Datenbank benötigen.
Am Ende habe ich dann für jedes Model eine Klasse, die ein einzelnes Objekt darstellt und Eine, die eine Liste davon darstellt.

Du kannst da ja auch was anderes zurück geben als ein ValidationResult. ValidationResult ist halt der Standard,

Siehst Du ein Problem darin, dass ich dann nur den String habe um den Fehler zu bestimmen?
Oder mache ich das Problem größer als es eigentlich ist?

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.830 Beiträge seit 2008
vor 7 Jahren

Das ist mehr oder weniger Dir überlassen.
Im DAL arbeite ich nicht mit Fehlernummern. Validierungsfehler im DAL sind Programmierfehler, die ich nicht ordentlich getestet hab.
Sie sind nur dazu da, dass nichts ungültiges in die DB kommt. Aber logisch abgefangen sollten sie eigentlich bereits in der BL.
Im DAL prüfe ich zB ein Feld auf Required via Data Annotations. Im BL würde ich zudem auch eben die Gültigkeit der E-Mail in der Form prüfen.

Ich könnte das an der Stelle auch weglassen und das Entity Framework übernehmen lassen.
Ist mir halt zu langsam bzw. verringert meine Wiederverwendbarkeit, wenn ich an manchen Stellen auch mit Dapper arbeite.

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 7 Jahren

Ok, dann mach ich das Problem wohl größer als es ist 😄

Ich danke dir

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.