2016-04-11 29 views
2

Ich weiß wirklich nicht, was ich in den Titel schreiben soll, also lies nicht zu viel hinein.Gibt es einen besseren Weg (C#)?

Wie auch immer, um zu erklären, mit was ich Probleme habe, ist dies. Ich habe drei Klassen: A, B und C. Die Klassenhierarchie ist, wie folgt:

< A - B < - C

(. So A ist die Basisklasse)

Die Anwendung In Frage steht eine Datenbank App. Es fragt Informationen mehrere Tabellen ab (A ist eine Tabelle, und B und C sind eins) und speichert es in einer Instanz von C. Dieser Teil funktioniert gut. Das Problem ist, wenn ich die durch B und C vertretene Tabelle aktualisieren möchte.

Um Standardcode für jeden Code zu vermeiden, verwende ich Reflektion, um eine Aktualisierungsabfrage von der Klasse zu generieren. Wenn ich jedoch eine Instanz einer C-Klasse übergebe, bedeutet dies, dass sie auch alle Elemente aus A auswählen wird, was eine separate Tabelle ist. Daher wird die Aktualisierungsabfrage falsch sein. Und da liegt mein Problem. I wollen alle Mitglieder von B und C ohne viel Code und mit einer sauberen, skalierbaren Lösung zu schreiben. Ich weiß einfach nicht wie.

(Ich verwende derzeit einen Ansatz, bei dem ich alle Mitglieder des Top-Level-Typs auswählen, dann alle Eltern der Klasse durchsuchen und alle diese Mitglieder auswählen und Mitglieder nicht mehr sammeln, wenn sie einen bestimmten Elterntyp findet, z ist eine schreckliche Lösung, denke ich.)

Alle diese Klassen enthält Variablen abgerufen aus der Datenbank und haben keine Methoden oder Felder. Irgendwelche guten Ideen, wie man dieses Problem angeht? Ich habe gehört, dass C# keine Mehrfachvererbung hat, ein Tool, das perfekt für diesen Job ist (danke C#!).

Ich hoffe, ich mache mich klar.

EDIT1: Um einige Fragen zu beantworten und zusätzlichen Kontext zu geben. Zunächst einmal, so sieht mein System heute aus. Um Daten abzufragen, habe ich zuerst eine Klasse X und einen Abfragetext. Die Abfrage wird für die Datenbank ausgeführt und gibt einige Zeilen zurück. Dann wird jede Zeile in eine Instanz von X konvertiert und zu einer Liste hinzugefügt. Der Code weiß, wie eine Zeile in ein X konvertiert wird, indem eine Reflektion verwendet wird und die tatsächlichen Variablen in X und der Name der Spalten in der abgerufenen Datenbankzeile betrachtet werden. Es nimmt dann die Spalte A und speichert diese Information in der Variablen A in X. Indem Sie also eine Klasse X erstellen und diese mit der Datenbankstruktur abgleichen, brauchen Sie nur eine weitere Tabelle mit Daten zu holen.

Manchmal müssen Sie Daten aus mehreren Tabellen abrufen und zusammenstellen. Um dies zu tun, brauche ich eine Klasse X, die mit den Informationen aus der Abfrage übereinstimmt. Es gibt viele Daten, die ich abrufe, und alle diese Daten haben eine gemeinsame Teilmenge, die aus Tabelle A abgerufen wurde. Der Rest wird von vielen anderen Tabellen abgerufen, die zusätzliche Daten enthalten. Daher sieht das Typsystem immer wie etwas A < - B aus, wobei A die gemeinsame Teilmenge aller von mir abgerufenen Daten ist. Dies funktioniert hervorragend für Abfragen. Ich kann zusätzliche Daten und eine Klasse hinzufügen und ich bin fertig. Kein Kesselblechcode mehr.

Aber das ist nur die halbe Geschichte. Ich muss diese Tabellen auch aktualisieren (ich brauche A nicht zu aktualisieren). Aber um das zu tun, muss ich die Daten trennen, die von der Tabelle A, der gemeinsamen Teilmenge, geholt werden. Hier ist, wie ich die Aktualisierung mache:

Connection.RunUpdateQuery (..., Utility.ToDictionary (Entry));

Dabei ist Entry die Klasse, die die Informationen enthält, die in der Zieltabelle aktualisiert werden sollen.Also konvertiere ich die Klasse in ein Dictionary, das den Spaltennamen und den Spaltenwert darstellt, und sende das RunUpdateQuery, das eine sql-Aktualisierungsanweisung generiert und an die Datenbank sendet. Auch das ist wirklich nett, da ich absolut keinen Boilerplate-Code schreiben muss.

Aber Utility.ToDicionary kann nicht wissen, welche Teilmenge von Informationen ich eigentlich einfügen möchte. Es nimmt einfach jede Variable in der Klasse und wandelt sie in ein Wörterbuch um, wobei der Name der Variablen der Spaltenname (d. H. Der Schlüssel des Wörterbuchs) ist. Wenn ich in diesem Fall ein C übergebe, möchte ich eigentlich nur B und C, weil sie Teil der Zieltabelle sind, die ich aktualisieren möchte. Die A-Teilmenge ist Teil einer anderen Tabelle, die ich nicht aktualisieren möchte.

Wenn es ein Framework gibt, das all diese Arbeit macht, bin ich dafür. Aber momentan habe ich nicht die Zeit, diesen Code neu zu schreiben. Also muss ich damit bis später warten.

Dies ist auch meine eigene Datenbank, die ich entwerfe. Ich bin für alles verantwortlich, was das Design des Projekts betrifft.

Ich möchte wirklich keine Abfragen generieren, auch wenn es nur bedeutet, ein Tool auszuführen, denn a) es bedeutet mehr Arbeit bei jeder Datenbankänderung und b) es bedeutet mehr Bugs, weil ich vergessen könnte, bestimmte Orte zu aktualisieren etwas ändert sich. Mit meiner aktuellen reflexionsbasierten Lösung muss ich nichts ändern. Ich muss nur die Datenbank und eine geeignete Klasse entwerfen (was ich sowieso tun muss, da ich die Zeilen aus der Datenbank in geeignete erstklassige Bürger im Code übersetzen muss, damit ich leichter mit ihnen arbeiten kann).

Die Verwendung von Attributen scheint keine gute Methode zu sein, weil es alles kontextabhängig ist. Der Aufrufer, der eine Tabelle aktualisieren möchte, muss auswählen können, welche Felder in der Datenbank aktualisiert werden sollen, jedoch nicht auf solch einer feinkörnigen Ebene. Der Aufrufer sollte einfach in der Lage sein, die zu aktualisierende Klasse sozusagen auszuwählen.

Vielleicht gibt dies einige Klarheit in meinem Problem.

EDIT2: Beispiele der Klasse A, B, C:

C:

public class PumpEntry: SignalEntryD 
{ 
    public uint? Addr; 
    public uint MasterlistIdx; 
    public bool MinDominant; 
    public uint? TriggerInterval; 
    public uint? SpDef; 
    public uint? MinDef; 
    public uint? MaxDef; 
    public uint? Step0Def; 
    public uint? Step1Def; 
    public uint? Step2Def; 
    public uint? Step3Def; 
    public uint? Step4Def; 
    public uint? Step5Def; 
    public uint? ExReqFacBACNet, ExSpFacBACNet; 
    public decimal? DeltaTX0Def, DeltaTX1Def, DeltaTX2Def, DeltaTX3Def; 
    public uint? DeltaTYMinDef, DeltaTY0Def, DeltaTY1Def; 
    public string DeltaTSensor1, DeltaTSensor2; 
    public int? ReqLimitMethodDef; 
} 

B:

public class SignalEntryD: DeviceEntry 
{ 
    public int? Channel; 
    public int? pCOeNum; 
} 

A:

public class DeviceEntry: DbType 
{ 
    public int Id; 
    public DeviceType Type; 
    public string Name; 
    public string CMCategory; 
    public bool Generate; 

    public new string ToString() { return Name; } 
} 
+4

'Ich höre C# hat keine Mehrfachvererbung, ein Werkzeug perfekt für diesen Job" Sie können stattdessen "Schnittstelle" verwenden. Eine Klasse kann mehrere Schnittstellen implementieren. –

+0

Sie möchten Mitglieder der Klasse 'C' bekommen, die nicht von Klasse' A' oder 'B' geerbt wurden? –

+1

Meiner Meinung nach ist der direkte Umgang mit Datenbanken in Ihrem Fall keine gute Lösung. Verwenden Sie ein ORM wie Entity Framework oder NHibernate. –

Antwort

4

Sie sollten wahrscheinlich Verwenden Sie eine ORM-Bibliothek wie z als Entity Framework oder NHibernate. Sie wissen, wie man mit Vererbung umgeht (sie bieten verschiedene Strategien, aus denen Sie wählen können). Dann müssen Sie überhaupt keinen Codebausteincode schreiben.

+0

würde ich argumentieren, abhängig von den spezifischen Bedürfnissen (die das OP nicht gegeben hat und wir nehmen nur an) , ein ORM * könnte * viel mehr * Vortex-Code als eine einfache reflexionsbasierte Lösung haben. Ich bin nicht downvoting, weil * wahrscheinlich * mit einem ORM wäre der Weg dafür zu gehen ... aber wir raten nur hier, da wir das globale Szenario nicht kennen. – Jcl

+0

@Jcl: Ich stimme zu, dass es Szenarien gibt, in denen ein vollwertiges ORM nicht passen würde. Allerdings sehe ich nicht, wo Sie Code schreiben würde, zum Beispiel eine Code-First-Ansatz mit EF. – Dejan

+0

Für den Anfang, indem Sie Code zuerst sagen, gehen Sie davon aus, dass das OP die Datenbank erstellen kann, wie es ihm gefällt, was nicht immer der Fall ist (es gibt viele Szenarien, in denen Sie von einem DBA-Team eine Datenbank erhalten) Sie müssen einen Kontext, Mappings, etc ... wieder definieren, es ist * wahrscheinlicher nicht der Fall * und EF (oder andere ORM) ist mehr als wahrscheinlich der Weg zu gehen: aber in SO Ich mag es, die gestellten Fragen zu beantworten, und ohne das Szenario zu kennen, möchte ich diese Art von Vorschlägen nicht geben (die gegenteilig sind und von den spezifischen Situationen abhängen). – Jcl

0

Sie sagen, Sie verwenden Reflexion. In thise Fall tun dies:

C myObject = new C();  
myObject.GetType().GetProperties(System.Reflection.BindingFlags.Public 
       | System.Reflection.BindingFlags.Instance 
       | System.Reflection.BindingFlags.DeclaredOnly) 

Sollten Sie nur die Eigenschaften speziell in B oder A in C, nicht deklariert geben. Von dort aus könnten Sie myObject.GetType().BaseType verwenden, was Ihnen B geben würde, und BaseType wäre A.

Check it in this fiddle

Ich habe einen groben Abfragegenerator hier: check it in this other fiddle, die alle Update-Abfragen benötigt erzeugt (wenn Sie einen C passieren, es wird Abfragen für Tabellen erzeugen A, B und C), mit Ausnahme der Typen Sie übergeben als Parameter.

Mit diesen Informationen können Sie Ihre Aktualisierungsabfragen problemlos dynamisch und für beliebige Hierarchieebenen generieren.

würde ich Sie spezifischere Code geben, aber sie schrieb keine

Nicht sagen, dies ist die perfekte Lösung, aber es ist das, was Sie fordern in Ihrer Frage

+0

Das Problem ist, dass ich Update-Abfragen dynamisch mit Reflektion erzeuge. Solche Methoden wissen nicht, wie die Klassenhierarchie aussieht. Manchmal kann es A <- B <- C <- D geben, wo ich B, C, D haben möchte. Manchmal kann es A <- B sein, wo ich nur B möchte. Das System muss gehandhabt werden Alles mit entsprechenden Informationen. Ich werde den Hauptbeitrag mit weiteren Informationen zu meinem derzeitigen Ansatz aktualisieren. Ich möchte lieber keine neuen Abfragen generieren. Ich würde sie lieber spontan mit wechselnden Datenstrukturen erzeugen. – Athena

+0

@Athena Blick auf die zweite Geige, die ich gemacht habe, generiert alle Abfragen dynamisch für die Klassenhierarchie, was auch immer es ist ... es verwendet die Eigenschaften auf jeder Ebene, um verschiedene Update-Abfragen zu generieren: https://dotnetfiddle.net/JOIX2t. Es ist ein sehr grober Generator, natürlich können Sie das erweitern (und Grenzen setzen, für welche Typen es generiert werden soll), aber ohne dass Sie irgendeinen Code zeigen oder echte Spezifikationen angeben, das ist das Beste, was ich geben kann. Dies entspricht - exakt - dem, was Sie in der Frage beschrieben haben, in der Art, wie Sie gefragt haben. – Jcl

+0

Ich habe den Beispielgenerator aktualisiert, um Ausnahmen einzufügen, für welche Typen er Abfragen generieren soll [überprüfen Sie es in dieser Geige] (https://dotnetfiddle.net/FVz6Ay) – Jcl

0

Ich denke, die typische C# Lösung für diese Markieren Sie Ihre Felder mit benutzerdefinierten Attributen und verwenden Sie diese bei der Reflexion, um zu entscheiden, ob sie enthalten sein sollen oder nicht.

public class DoSerializeAttribute : Attribute {} 

public class C : B { 
    [DoSerialize] 
    public MyMember { get; set; } 
} 

Und später in Ihrem Reflexion Code können Sie GetCustomAttribute Methode verwenden.

0

Ohne ORM zu Lernen (das ist ein richtige Sache, kann aber viel des Guten in einem einfachen Fall sein und mit den Worten Overkill ich meine Lernkurve) Sie LINQ-to-SQL als Modell verwenden können Ihre Daten stattdessen zugreifen Erstellen C Klasse selbst. Erstellen Sie eine Datei (siehe z. B. here in Bezug auf was es ist). Als Ergebnis erhalten Sie erhalten Sie Tabellenklasse Code-generieren, dann Sie es nur

using (var context = new SomeContext()) // static connection string 
{ 
    var query = context.SomeTable.AsQueryable(); 
    if (SelectedFilter == Today) 
     query = query.Where(o => o.Id >= DateTime.Today); 
    ... 
    // constructing ViewModel items (WPF, MVVM) 
    foreach (var item in query) 
     items.Add(new Item() 
     { 
      Id = item.Id, 
      ... 
     } 
} 

Item (B in Ihrem Beispiel) verwenden, ist eine einfache Klasse Werte zu halten. Sie können andere Eigenschaften von Item mithilfe einer anderen Abfrage oder eines anderen Kontexts (einer anderen Tabelle oder Datenbank) auffüllen.

Wenn Sie Datenbank aktualisieren möchten Sie einfach tun

using (var context = new SomeContext()) 
{ 
    var change = context.SomeTable.First(o => o.Id == item.Id); 
    change.Comment = item.Comment; 
    ... 
    context.SubmitChanges(); 
} 

Grundsätzlich Sie diese Methoden einmal für Ihre fertige Ansichtsmodell Artikel schreiben (kann eine komplizierte Abfrage zu viele Tabellen oder mehrere Abfragen an verschiedene Datenbanken sein). Das Aktualisieren des Teils kann Methoden von Item oder besser von ViewModel sein (weil es optimiert werden kann, e.q., wenn Sie nur Comment ändern, müssen Sie andere Felder nicht aktualisieren und andere Abfragen ausführen).

Kesselplatte? Nicht wirklich, schaue in SomeContext generierte CS-Datei, um einige zu sehen.

0

Um ehrlich zu sein, ich persönlich denke, Ihre aktuelle Lösung ist ein Durcheinander. Es gibt unendlich viele Tools für diese Art von Sachen, warum neu erfinden (und sehr schlecht, um ehrlich zu sein) das Rad?

Wie auch immer, versucht, Ihr Problem in Ihrem speziellen Setup zu lösen, hier ist das, was ich tun würde:

First off, die Vererbung loszuwerden. Sie sollten es überhaupt nicht benutzen. Inheritante ist definitiv kein Werkzeug, das als Mittel zur Vermeidung von Datenduplikation verwendet werden soll, die einfache Vorstellung ist abscheulich.

Sie haben zwei verschiedene Tabellen in Ihrer Datenbank, codieren Sie sie als solche.

  1. DbType
  2. PumpEntry

Ich habe keine Ahnung, warum Sie die Zwischen SignalEntryD benötigen. Wenn es keine Tabelle in Ihrer DB ist, dann sollte es nirgendwo in Ihrem Code erscheinen (ich vermute es auch aufgrund der Code-Duplizierung).

Seien Sie konsistent: Wenn die Felder Channel und pCoeNum in verschiedenen Tabellen in Ihrer Datenbank dupliziert werden, dann kopieren Sie sie einfach in Ihre Entitäten. Erstellen Sie andernfalls eine Tabelle in Ihrer DB und modellieren Sie sie dann in Ihren Entitäten (wie Sie es mit DbType tun). Verwechsle die Dinge nicht, mach es an beiden Enden gleich.

Aus Gründen, die später klar werden, lassen Sie beide Entitäten eine "Dummy" -Schnittstelle implementieren ITable (Typ Sicherheit) und einen standardmäßigen parameterlosen Konstruktor (einschließlich all Ihrer Eigenschaften/Felder natürlich).

Jetzt ist das Problem, wenn Sie richtig verstehen, dass Sie eine Dictionary<string, object> mit Benutzer aktualisierte Werte erhalten und Sie müssen eine bestimmte Tabelle aktualisieren, das Problem, dass das Dicitonary Felder enthalten kann, die zu verschiedenen Tabellen gehören (ich gewann Wenn du nicht weißt, wie du zu diesem Problem gekommen bist, werde ich einfach mitfahren ...).

Na, dann erstellen Sie einfach eine Möglichkeit, jede Einheit aus einer zufälligen dicitionary mit Reflexion zu bauen (mein Code verwendet Eigenschaften, aber es ist gleichbedeutend mit Feldern):

public static T CreateTable<T>(IDictionary<string, object> values) where T: ITable, new() 
{ 
    var table = new T(); 

    foreach (var propInfo in typeof(T).GetProperties()) 
    { 
      if (values.ContainsKey(propInfo.Name)) 
      { 
       propInfo.SetValue(table, values[propInfo.Name]); 
      } 
    } 

    return table; //note that any property not defined in the dictionary will be initialized to the field's type default value. 
} 

Und jetzt, würden Sie es verwenden, wie folgt:

Verwandte Themen