المخطط (Schema)
عندما تستخدم Isar لتخزين بيانات تطبيقك، فإنك تتعامل مع المجموعات (collections). المجموعة تشبه جدول قاعدة البيانات في قاعدة بيانات Isar المرتبطة، ويمكنها فقط احتواء نوع واحد من كائنات Dart. يمثل كل كائن في المجموعة صفاً من البيانات في المجموعة المقابلة.
يُطلق على تعريف المجموعة اسم "المخطط" (schema). سيقوم مولد Isar (Isar Generator) بالعمل الشاق نيابة عنك وسيقوم بإنشاء معظم الكود الذي تحتاجه لاستخدام المجموعة.
تشريح المجموعة
تقوم بتعريف كل مجموعة Isar عن طريق إضافة التعليق التوضيحي @collection أو @Collection() للفئة (class). تتضمن مجموعة Isar حقولاً لكل عمود في الجدول المقابل في قاعدة البيانات، بما في ذلك الحقل الذي يمثل المفتاح الأساسي (primary key).
الكود التالي هو مثال لمجموعة بسيطة تحدد جدول User (مستخدم) مع أعمدة للمعرف (ID)، والاسم الأول، واسم العائلة:
@collection
class User {
late int id;
String? firstName;
String? lastName;
}
:::tip لحفظ حقل ما، يجب أن يكون لدى Isar وصول إليه. يمكنك التأكد من وصول Isar إلى الحقل عن طريق جعله عاماً (public) أو عن طريق توفير طرق الحصول والتعيين (getter and setter). :::
هناك بضع معلمات اختيارية لتخصيص المجموعة:
| التكوين (Config) | الوصف |
|---|---|
inheritance |
التحكم في ما إذا كان سيتم تخزين حقول الفئات الأب والـ mixins في Isar. مفعل افتراضياً. |
accessor |
يسمح لك بإعادة تسمية أداة الوصول الافتراضية للمجموعة (على سبيل المثال isar.contacts لمجموعة Contact). |
ignore |
يسمح بتجاهل خصائص معينة. يتم احترام هذه أيضاً للفئات العليا (super classes). |
معرف Isar (Isar Id)
يجب على كل فئة مجموعة تعريف خاصية معرف بنوع Id تحدد الكائن بشكل فريد. Id هو مجرد اسم مستعار لـ int يسمح لمولد Isar بالتعرف على خاصية المعرف.
يقوم Isar تلقائياً بفهرسة حقول المعرف، مما يسمح لك بالحصول على الكائنات وتعديلها بناءً على معرفها بكفاءة.
يمكنك إما تعيين المعرفات بنفسك أو الطلب من Isar تعيين معرف يزداد تلقائياً (auto-increment). إذا كان حقل id هو null وليس نهائياً (final)، فسيقوم Isar بتعيين معرف يزداد تلقائياً. إذا كنت تريد معرفاً يزداد تلقائياً وغير قابل للقيمة الفارغة (non-nullable)، يمكنك استخدام Isar.autoIncrement بدلاً من null.
:::tip لا يتم إعادة استخدام المعرفات التي تزداد تلقائياً عند حذف كائن. الطريقة الوحيدة لإعادة تعيين هذه المعرفات هي مسح قاعدة البيانات. :::
إعادة تسمية المجموعات والحقول
بشكل افتراضي، يستخدم Isar اسم الفئة كاسم للمجموعة. وبالمثل، يستخدم Isar أسماء الحقول كأسماء للأعمدة في قاعدة البيانات. إذا كنت تريد أن يكون للمجموعة أو الحقل اسم مختلف، أضف التعليق التوضيحي @Name. يوضح المثال التالي الأسماء المخصصة للمجموعة والحقول:
@collection
@Name("User")
class MyUserClass1 {
@Name("id")
Id myObjectId;
@Name("firstName")
String theFirstName;
@Name("lastName")
String familyNameOrWhatever;
}
خاصة إذا كنت تريد إعادة تسمية حقول Dart أو الفئات المخزنة بالفعل في قاعدة البيانات، يجب عليك التفكير في استخدام التعليق التوضيحي @Name. وإلا، ستقوم قاعدة البيانات بحذف وإعادة إنشاء الحقل أو المجموعة.
تجاهل الحقول
يقوم Isar بحفظ جميع الحقول العامة لفئة المجموعة. من خلال إضافة التعليق التوضيحي @ignore لخاصية أو أداة الحصول (getter)، يمكنك استبعادها من الحفظ، كما هو موضح في مقتطف الكود التالي:
@collection
class User {
late int id;
String? firstName;
String? lastName;
@ignore
String? password;
}
في الحالات التي ترث فيها مجموعة حقولاً من مجموعة أب، عادة ما يكون من الأسهل استخدام خاصية ignore في التعليق التوضيحي @Collection:
@collection
class User {
Image? profilePicture;
}
@Collection(ignore: {'profilePicture'})
class Member extends User {
late int id;
String? firstName;
String? lastName;
}
إذا كانت المجموعة تحتوي على حقل بنوع غير مدعوم من قبل Isar، فيجب عليك تجاهل الحقل.
:::warning ضع في اعتبارك أنه ليس من الممارسات الجيدة تخزين معلومات في كائنات Isar لا يتم حفظها. :::
الأنواع المدعومة
يدعم Isar أنواع البيانات التالية:
boolbyteshortintfloatdoubleDateTimeDurationStringList<bool>List<byte>List<short>List<int>List<float>List<double>List<DateTime>List<Duration>List<String>
بالإضافة إلى ذلك، يتم دعم الكائنات المضمنة (embedded objects) والأنواع المعددة (enums). سنغطي هذه أدناه.
byte, short, float
بالنسبة للعديد من حالات الاستخدام، لا تحتاج إلى النطاق الكامل لعدد صحيح 64 بت أو عدد عشري مزدوج (double). يدعم Isar أنواعاً إضافية تسمح لك بتوفير المساحة والذاكرة عند تخزين أرقام أصغر.
| النوع | الحجم بالبايت | النطاق |
|---|---|---|
| byte | 1 | من 0 إلى 255 |
| short | 4 | من -2,147,483,647 إلى 2,147,483,647 |
| int | 8 | من -9,223,372,036,854,775,807 إلى 9,223,372,036,854,775,807 |
| float | 4 | من -3.4e38 إلى 3.4e38 |
| double | 8 | من -1.7e308 إلى 1.7e308 |
أنواع الأرقام الإضافية هي مجرد أسماء مستعارة لأنواع Dart الأصلية، لذا فإن استخدام short على سبيل المثال يعمل بنفس طريقة استخدام int.
إليك مثال لمجموعة تحتوي على جميع الأنواع المذكورة أعلاه:
@collection
class TestCollection {
late int id;
late byte byteValue;
short? shortValue;
int? intValue;
float? floatValue;
double? doubleValue;
}
يمكن أيضاً استخدام جميع أنواع الأرقام في القوائم. لتخزين البايتات، يجب استخدام List<byte>.
الأنواع القابلة للقيمة الفارغة (Nullable types)
يعد فهم كيفية عمل القيم الفارغة (nullability) في Isar أمراً ضرورياً: أنواع الأرقام لا تمتلك تمثيلاً مخصصاً لـ null. بدلاً من ذلك، يتم استخدام قيمة محددة:
| النوع | VM |
|---|---|
| short | -2147483648 |
| int | int.MIN |
| float | double.NaN |
| double | double.NaN |
تمتلك أنواع bool و String و List تمثيلاً منفصلاً لـ null.
يسمح هذا السلوك بتحسين الأداء، ويسمح لك بتغيير قابلية الحقول للقيم الفارغة بحرية دون الحاجة إلى ترحيل البيانات (migration) أو كود خاص للتعامل مع قيم null.
:::warning
نوع byte لا يدعم القيم الفارغة (null values).
:::
التاريخ والوقت (DateTime)
لا يقوم Isar بتخزين معلومات المنطقة الزمنية لتواريخك. بدلاً من ذلك، يقوم بتحويل الـ DateTime إلى توقيت UTC قبل تخزينها. يعيد Isar جميع التواريخ بالتوقيت المحلي.
يتم تخزين الـ DateTime بدقة الميكروثانية. في المتصفحات، يتم دعم دقة الميلي ثانية فقط بسبب قيود JavaScript.
المدة (Duration)
يتم تخزين المدد (Durations) بدقة الميلي ثانية.
النوع المعدد (Enum)
يسمح Isar بتخزين واستخدام الـ enums مثل أنواع Isar الأخرى. ومع ذلك، يجب عليك اختيار كيفية تمثيل Isar للـ enum على القرص. يدعم Isar أربع استراتيجيات مختلفة:
| EnumType | الوصف |
|---|---|
ordinal |
يتم تخزين فهرس الـ enum كـ byte. هذا فعال جداً ولكنه لا يسمح بالـ enums القابلة للقيمة الفارغة. |
ordinal32 |
يتم تخزين فهرس الـ enum كـ short (عدد صحيح 4 بايت). |
name |
يتم تخزين اسم الـ enum كـ String. |
value |
يتم استخدام خاصية مخصصة لاسترداد قيمة الـ enum. |
:::warning
تعتمد استراتيجيات ordinal و ordinal32 على ترتيب قيم الـ enum. إذا قمت بتغيير الترتيب، فستعيد قواعد البيانات الموجودة قيماً غير صحيحة.
:::
دعنا نلقي نظرة على مثال لكل استراتيجية:
@collection
class EnumCollection {
late int id;
@enumerated // نفس EnumType.ordinal
late TestEnum byteIndex; // لا يمكن أن يكون قابلاً للقيمة الفارغة
@Enumerated(EnumType.ordinal)
late TestEnum byteIndex2; // لا يمكن أن يكون قابلاً للقيمة الفارغة
@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;
}
بالطبع، يمكن أيضاً استخدام الـ Enums في القوائم.
الكائنات المضمنة (Embedded objects)
غالباً ما يكون من المفيد وجود كائنات متداخلة في نموذج مجموعتك. لا يوجد حد لمدى عمق تداخل الكائنات. ومع ذلك، ضع في اعتبارك أن تحديث كائن متداخل بعمق سيتطلب كتابة شجرة الكائنات بالكامل في قاعدة البيانات.
@collection
class Email {
late int id;
String? title;
Recepient? recipient;
}
@embedded
class Recepient {
String? name;
String? address;
}
يمكن للكائنات المضمنة أن تكون قابلة للقيمة الفارغة وأن ترث من كائنات أخرى. المتطلب الوحيد هو أن يتم تمييزها بالتعليق التوضيحي @embedded وأن يكون لها منشئ افتراضي بدون معلمات مطلوبة.