Schema

Wenn du Isar benutzt, um deine App-Daten zu speichern, dann hast du mit Collections zu tun. Eine Collection ist wie die Tabelle einer Datenbank in der angeschlossenen Isar-Datenbank und kann nur einen einzigen Typen von Dart Objekt enthalten. Jedes Collection-Objekt repräsentiert eine Zeile mit Daten in der zugehörigen Collection.

Die Definition einer Collection wird "Schema" genannt. Der Isar-Generator macht die meiste Arbeit für dich und generiert den Großteil des Codes den du benötigst, um die Collection zu benutzen.

Aufbau einer Collection

Jede Collection in Isar wird über die Annotation @collection oder @Collection() an einer Klasse definiert. Eine Isar-Collection enthält Felder für jede Spalte in der zugehörigen Tabelle der Datenbank, auch eine, die dem Primärschlüssel entspricht.

Der folgende Code ist ein Beispiel einer simplen Collection, welche eine User-Tabelle mit den Spalten ID, Vorname und Nachname definiert:

@collection
class User {
  late int id;

  String? firstName;

  String? lastName;
}

Tipp

Um ein Feld persistent zu machen, muss Isar Zugriff auf das Feld haben. Du kannst sicherstellen, dass Isar Zugriff auf ein Feld hat, indem du es öffentlich machst, oder indem du Getter- und Setter-Methoden definierst.

Es gibt ein paar optionale Parameter, um die Collection anzupassen:

KonfigurationBeschreibung
inheritanceSteuert, ob Felder von Elternklassen und Mixins in Isar gespeichert werden. Standardmäßig aktiviert.
accessorErlaubt dir den Standardnamen des Collection-Accessors umzubenennen (zum Beispiel zu isar.contacts für die Contact-Collection).
ignoreErlaubt es bestimmte Eigenschaften zu ignorieren. Diese werden auch bei Superklassen angewendet.

Isar ID

Jede Collection-Klasse muss eine ID-Eigenschaft vom Typen Id definieren, die ein Objekt eindeutig identifiziert. Id ist eigentlich nur ein Alias für int, der es dem Isar Generator ermöglicht die ID-Eigenschaft zu erkennen.

Isar indiziert ID Felder automatisch, was dir ermöglicht Objekte effizient anhand ihrer ID zu erhalten und modifizieren.

Du kannst eintweder IDs selbst zuweisen oder Isar fragen eine sich automatisch erhöhende ID festzulegen. Wenn das id-Feld null und nicht final ist, wird Isar eine auto-increment ID setzen. Wenn du eine nicht null-bare auto-increment ID haben möchtest, kannst du Isar.autoIncrement anstatt von null verwenden.

Tipp

Auto-increment IDs werden nicht wiederverwendet, wenn ein Objekt gelöscht wird. Der einzige Weg auto-increment IDs zurückzusetzen ist die Datenbank zu leeren.

Collections und Felder umbenennen

Isar benutzt standardmäßig den Klassennamen als Collectionnamen. Genauso verwendet Isar in der Datenbank Feldnamen als Spaltennamen. Wenn du willst, dass eine Collection oder ein Feld einen anderen Namen hat, dann füge die Annotation @Name hinzu. Das folgende Beispiel demonstriert angepasste Namen für Collections und Felder:

@collection
@Name("User")
class MyUserClass1 {

  @Name("id")
  Id myObjectId;

  @Name("firstName")
  String theFirstName;

  @Name("lastName")
  String familyNameOrWhatever;
}

Du solltest besonders dann, wenn du Dart-Felder oder -Klassen umbenennen willst, überlegen, die @Name-Annotation zu verwenden. Sonst wird die Datenbank das Feld oder die Collection löschen und neu erzeugen.

Felder ignorieren

Isar stell sicher, dass alle öffentlichen Felder einer Collecion-Klasse erhalten bleiben. Wenn du eine Eigenschaft oder einen Getter mit @ignore annotierst, kannst du diese von der Sicherstellung ausschließen, wie im folgenden Code-Schnipsel gezeigt:

@collection
class User {
  late int id;

  String? firstName;

  String? lastName;

  @ignore
  String? password;
}

In Fällen, in denen deine Collection Felder von der Eltern-Collection erhält, ist es meist leichter die ignore-Eigenschaft der @Collection-Annotation zu verwenden:

@collection
class User {
  Image? profilePicture;
}

@Collection(ignore: {'profilePicture'})
class Member extends User {
  late int id;

  String? firstName;

  String? lastName;
}

Wenn eine Collection ein Feld mit einem Typen enthält, der nicht von Isar unterstützt wird, musst du das Feld ignorieren.

Warnung

Beachte, dass es keine gute Vorgehensweise ist, Informationen in Isar-Objekten zu speichern, die nicht gesichert werden.

Unterstützte Typen

Isar unterstützt die folgenden Datentypen:

  • bool
  • byte
  • short
  • int
  • float
  • double
  • DateTime
  • String
  • List<bool>
  • List<byte>
  • List<short>
  • List<int>
  • List<float>
  • List<double>
  • List<DateTime>
  • List<String>

Zusätzlich sind eingebettete Objekte und Enums unterstützt. Wir behandeln diese weiter unten.

byte, short, float

In vielen Fällen benötigst du nicht den gesamten Bereich eines 64-bit Integers oder Doubles. Isar unterstützt zusätzliche Typen, die es dir erlauben Speicherpaltz beim Speichern kleinerer Zahlen zu sparen.

TypGröße in bytesBereich
byte10 bis 255
short4-2.147.483.647 bis 2.147.483.647
int8-9.223.372.036.854.775.807 bis 9.223.372.036.854.775.807
float4-3,4e38 bis 3,4e38
double8-1,7e308 bis 1,7e308

Die zusätzlichen Zahl-Typen sind nur Aliase für die nativen Dart-Typen, also beispielsweise short zu benutzen funktioniert genau, wie wenn du int nutzen würdest.

Hier ist eine Beispiel-Collection, welche alle der eben genannten Typen enthält:

@collection
class TestCollection {
  late int id;

  late byte byteValue;

  short? shortValue;

  int? intValue;

  float? floatValue;

  double? doubleValue;
}

Alle Zahlen-Typen können auch in Listen verwendet werden. Um Bytes zu speichern solltest du List<byte> benutzen.

Null-bare Typen

Zu verstehen wie Null-barkeit in Isar funktioniert ist essentiell: Zahlen-Typen haben KEINE gemeinsame festgelegte null-Darstellung. Stattdessen wird ein bestimmter Wert genutzt:

TypVM
short-2147483648
intint.MIN
floatdouble.NaN
doubledouble.NaN

bool, String, und List haben eine seperate null-Darstellung.

Dieses Verhalten erlaubt Leistungsverbesserungen, und ermöglicht es die Null-barkeit deiner Felder frei zu ändern, ohne eine Migration oder speziellen Code zum handhaben von null-Werten zu benötigen.

Warnung

Der byte-Typ unterstützt keine Null-Werte.

DateTime

Isar speichert keine Zeitzoneninformationen von deinen Daten. Stattdessen wandelt es DateTimes zu UTC um, bervor es diese speichert. Isar gibt jedes Datum in lokaler Zeit zurück.

DateTimes werden mit Mikrosekunden-Präzision gespeichert. In Browsern ist, aufgrund von JavaScript-Limitationen, nur Millisekunden-Präzision möglich.

Enum

Isar ermöglicht es Enums wie andere Isar-Typen zu nutzen und zu speichern. Du musst aber wählen, wie Isar den Enum auf dem Datenträger abbilden soll. Isar unterstützt vier verschiedene Strategien:

Enum-TypBeschreibung
ordinalDer Index des Enums wird als byte gespeichert. Das ist sehr effizienzt, aber erlaubt keine Null-baren Enums.
ordinal32Der Index des Enums wird als short (4-Byte-Integer) gespeichert. Erlaubt keine Null-baren Enums.
nameDer Name des Enums wird als String gespeichert.
valueEine angepasste Eigenschaft wird genutzt, um den Enum-Wert abzurufen.

Warnung

ordinal und ordinal32 basieren auf der Reihenfolge der Enum-Werte. Wenn du die Reihenfolge änderst, werden existierende Datenbanken falsche Werte zurückgeben.

Schauen wir uns ein Beispiel für jede Strategie an.

@collection
class EnumCollection {
  late int id;

  @enumerated // entspricht EnumType.ordinal
  late TestEnum byteIndex; // ist nicht Null-bar

  @Enumerated(EnumType.ordinal)
  late TestEnum byteIndex2; // ist nicht Null-bar

  @Enumerated(EnumType.ordinal32)
  TestEnum? shortIndex;

  @Enumerated(EnumType.name)
  TestEnum? name;

  @Enumerated(EnumType.value, 'myValue')
  TestEnum? myValue;
}

enum TestEnum {
  first(10),
  second(100),
  third(1000);

  const TestEnum(this.myValue);

  final short myValue;
}

Natürlich können Enums auch in Listen benutzt werden.

Eingebettete Objekte

Es ist oft hilfreich verschachtelte Objekte in deinem Collection-Modell zu haben. Daher gibt es keine Begrenzung, wie tief die Verschachtelung von Objekten sein kann. Beachte jedoch, dass der gesamte Objekt-Baum in die Datenbank geschrieben werden muss, um ein sehr tief verschachteltes Objekt zu aktualisieren.

@collection
class Email {
  late int id;

  String? title;

  Recepient? recipient;
}

@embedded
class Recepient {
  String? name;

  String? address;
}

Eingebettete Objekte können Null-bar sein und andere Objekte erweitern. Die einzige Voraussetzung ist, dass sie mit @embedded annotiert werden und einen Standardkonstruktor ohne erforderliche Parameter haben.