11 Stimmen

Ist es eine gute Idee, einen benutzerdefinierten Typ für den Primärschlüssel jeder Datentabelle zu erstellen?

Wir haben eine Menge Code, der über " Ids " von Datenzeilen; diese sind meist Ints oder Guids. Ich könnte diesen Code sicherer machen, indem ich eine andere Struktur für die ID jeder Datenbanktabelle. Dann hilft die Typüberprüfung, Fälle zu finden, in denen die falsche ID übergeben wird.

Z.B. hat die Tabelle Person eine Spalte namens PersonId und wir haben Code wie:

DeletePerson(int personId)
DeleteCar(int carId)

Wäre es besser, sie zu haben:

struct PersonId
{
   private int id;
   // GetHashCode etc....
}

DeletePerson(PersionId persionId)
DeleteCar(CarId carId)
  • Hat jemand Erfahrungen aus dem wirklichen Leben Erfahrungen damit?

  • Ist es den Aufwand wert?

  • Oder mehr Schmerz als es wert ist?

(Das würde es auch einfacher machen, den Datentyp des Primärschlüssels in der Datenbank zu ändern, deshalb habe ich mir diese Idee überhaupt erst ausgedacht)


Bitte sagen Sie nicht, dass Sie ein ORM oder eine andere große Änderung am Systemdesign verwenden sollen, denn ich weiß, dass ein ORM eine bessere Option wäre, aber das liegt derzeit nicht in meiner Macht. Ich kann jedoch kleinere Änderungen wie die oben genannten an dem Modul vornehmen, an dem ich derzeit arbeite.

Aktualisierung: Beachten Sie, dass es sich hier nicht um eine Webanwendung handelt und die Ids im Speicher gehalten und mit WCF weitergegeben werden, so dass am Rand keine Konvertierung in/aus Strings erfolgt. Es gibt keinen Grund, warum die WCF-Schnittstelle nicht den Typ PersonId usw. verwenden kann. Der PersonId-Typ usw. könnte sogar im WPF/Winforms UI-Code verwendet werden.

Die einzige inhärente "untypisiert" Teil des Systems ist die Datenbank.


Es scheint eine Kosten-Nutzen-Abwägung zu sein, ob man Zeit damit verbringt, Code zu schreiben, den der Compiler besser überprüfen kann, oder ob man die Zeit damit verbringt, mehr Unit-Tests zu schreiben. Ich entscheide mich eher dafür, die Zeit für Tests zu verwenden, da ich zumindest einige Unit-Tests in der Codebasis sehen möchte.

4voto

Jeff Sternal Punkte 46528

Es ist schwer vorstellbar, wie sich das lohnen könnte: Ich empfehle, dies nur als letzten Ausweg zu tun und nur dann, wenn die Leute während der Entwicklung tatsächlich Identifikatoren vermischen oder Schwierigkeiten haben, sie auseinanderzuhalten.

Insbesondere in Webanwendungen bietet es nicht einmal die Sicherheit, die Sie sich erhoffen: In der Regel werden Sie ohnehin Zeichenketten in Ganzzahlen konvertieren. Es gibt einfach zu viele Fälle, in denen Sie sich dabei ertappen, wie Sie dummen Code wie diesen schreiben:

int personId;
if (Int32.TryParse(Request["personId"], out personId)) { 
    this.person = this.PersonRepository.Get(new PersonId(personId));
}

Der Umgang mit komplexen Zuständen im Speicher verbessert sicherlich die Argumente für stark typisierte IDs, aber ich denke Arthurs Idee ist noch besser: Um Verwechslungen zu vermeiden, verlangen Sie eine Entitätsinstanz anstelle eines Bezeichners. In manchen Situationen könnte dies aus Leistungs- und Speichererwägungen unpraktisch sein, aber selbst diese sollten so selten sein, dass die Codeüberprüfung ohne die negativen Nebeneffekte genauso effektiv wäre (ganz im Gegenteil!).

Ich habe an einem System gearbeitet, das dies tat, und es hat keinen wirklichen Nutzen gebracht. Wir hatten keine Unklarheiten wie die, die Sie beschreiben, und im Hinblick auf die Zukunftssicherheit wurde es etwas schwieriger, neue Funktionen zu implementieren, ohne dass es sich auszahlte. (Der Datentyp einer ID hat sich jedenfalls in den letzten zwei Jahren nicht geändert - das könnte sicherlich irgendwann passieren, aber soweit ich weiß, ist der Return on Investment dafür derzeit negativ).

2voto

Arthur Thomas Punkte 4947

Ich würde dafür keinen speziellen Ausweis machen. Dies ist hauptsächlich eine Frage des Testens. Sie können den Code testen und sicherstellen, dass er tut, was er tun soll.

Sie können eine Standardmethode für Ihr System schaffen, die die künftige Wartung erleichtert (ähnlich wie das, was Sie erwähnen), indem Sie das gesamte zu bearbeitende Objekt übergeben. Wenn Sie Ihren Parameter (int personID) benannt und dokumentiert haben, sollte jeder nicht böswillige Programmierer in der Lage sein, den Code beim Aufruf dieser Methode effektiv zu verwenden. Durch die Übergabe eines ganzen Objekts wird der von Ihnen gewünschte Typabgleich durchgeführt, und das sollte als standardisierte Methode ausreichen.

Eine spezielle Struktur zu haben, um sich dagegen zu schützen, sehe ich einfach als zusätzliche Arbeit für wenig Nutzen. Selbst wenn Sie dies taten, könnte jemand kommen und finden Sie einen bequemen Weg, um eine "Helfer" Methode zu machen und zu umgehen, was auch immer Struktur, die Sie an Ort und Stelle setzen sowieso so ist es wirklich keine Garantie.

2voto

HardCode Punkte 6311

Sie können sich einfach für GUIDs entscheiden, wie Sie selbst vorgeschlagen haben. Dann müssen Sie nicht befürchten, dass Sie eine Personen-ID von "42" an DeleteCar() übergeben und versehentlich das Auto mit der ID 42 löschen. GUIDs sind eindeutig; wenn Sie in Ihrem Code aufgrund eines Programmierfehlers eine Personen-GUID an DeleteCar übergeben, wird diese GUID keine PK eines beliebigen Autos in der Datenbank sein.

2voto

user7116 Punkte 61589

Sie könnten eine einfache Id Klasse, mit deren Hilfe im Code zwischen den beiden unterschieden werden kann:

public class Id<T>
{
    private int RawValue
    {
        get;
        set;
    }

    public Id(int value)
    {
        this.RawValue = value;
    }

    public static explicit operator int (Id<T> id) { return id.RawValue; }

    // this cast is optional and can be excluded for further strictness
    public static implicit operator Id<T> (int value) { return new Id(value); }
}

Wird so verwendet:

class SomeClass
{
     public Id<Person> PersonId { get; set; }
     public Id<Car> CarId { get; set; }
}

Wenn man davon ausgeht, dass Ihre Werte nur aus der Datenbank abgerufen werden, ist es nicht möglich, die beiden Werte anstelle der anderen zu verwenden, es sei denn, Sie wandeln den Wert explizit in eine Ganzzahl um.

1voto

Godeke Punkte 15741

Ich sehe in diesem Fall keinen großen Nutzen in einer benutzerdefinierten Prüfung. Sie möchten vielleicht Ihre Testsuite aufpeppen, um zu überprüfen, dass zwei Dinge geschehen:

  1. Ihr Datenzugriffscode funktioniert immer so, wie Sie es erwarten (d. h., Sie laden keine inkonsistenten Schlüsselinformationen in Ihre Klassen und werden deshalb nicht missbraucht).
  2. dass Ihr "Round-Trip"-Code wie erwartet funktioniert (d. h. dass das Laden eines Datensatzes, das Vornehmen einer Änderung und das Zurückspeichern nicht in irgendeiner Weise Ihre Geschäftslogikobjekte beschädigt).

Eine Datenzugriffs- (und Geschäftslogik-) Schicht, der Sie vertrauen können, ist entscheidend für die Bewältigung der größeren Probleme, auf die Sie bei der Umsetzung der eigentlichen Geschäftsanforderungen stoßen werden. Wenn Ihre Datenschicht unzuverlässig ist, werden Sie viel Mühe aufwenden müssen, um Probleme auf dieser Ebene zu verfolgen (oder schlimmer noch, zu umgehen), die auftauchen, wenn Sie das Subsystem belasten.

Wenn Ihr Datenzugriffscode stattdessen robust gegenüber falscher Verwendung ist (was Ihre Testsuite Ihnen beweisen sollte), dann können Sie sich auf den höheren Ebenen ein wenig entspannen und darauf vertrauen, dass sie bei Missbrauch Ausnahmen (oder wie auch immer Sie damit umgehen) auslösen werden.

Der Grund, warum die Leute ein ORM vorschlagen, ist, dass viele dieser Probleme von solchen Tools zuverlässig gelöst werden. Wenn Ihre Implementierung so weit fortgeschritten ist, dass ein solcher Wechsel schmerzhaft wäre, denken Sie einfach daran, dass Ihre Datenzugriffsschicht auf niedriger Ebene so robust sein muss wie ein gutes ORM, wenn Sie wirklich in der Lage sein wollen, Ihrem Datenzugriff zu vertrauen (und ihn damit bis zu einem gewissen Grad zu vergessen).

Anstelle einer benutzerdefinierten Validierung könnte Ihre Testsuite Code injizieren (über Dependency Injection), der robuste Tests Ihrer Keys durchführt (indem er auf die Datenbank zugreift, um jede Änderung zu überprüfen), während die Tests laufen, und der Produktionscode injiziert, der solche Tests aus Leistungsgründen auslässt oder einschränkt. Ihre Datenschicht wird bei fehlgeschlagenen Schlüsseln Fehler ausgeben (wenn Sie Ihre Fremdschlüssel dort korrekt eingerichtet haben), so dass Sie auch in der Lage sein sollten, diese Ausnahmen zu behandeln.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X