قواعد بيانات Isar في تطبيقات Flutter

في الآونه الأخيرة تم نشر الكثير من الحُزم للتعامل مع البيانات في تطبيقات Flutter من حفظ البيانات وإسترجاعها، ولعل أشهر قواعد البيانات sqflite و Hive. مع اختلافهم في طريقة حفظ واسترجاع البيانات ولكن حققوا نجاح كبير جدًا من خلال الآداء في القراءة والكتابة او أدائها في فعالية وسرعة التطبيق. لكن مطوري Hive قدموا حُزمة أُخرى واعده ايضًا وهي Isar.

في الآونه الأخيرة تم نشر الكثير من الحُزم للتعامل مع البيانات في تطبيقات Flutter من حفظ البيانات وإسترجاعها، ولعل أشهر قواعد البيانات sqflite و Hive. مع اختلافهم في طريقة حفظ واسترجاع البيانات ولكن حققوا نجاح كبير جدًا من خلال الآداء في القراءة والكتابة او أدائها في فعالية وسرعة التطبيق. لكن مطوري Hive قدموا حُزمة أُخرى واعده ايضًا وهي Isar.

🗄 لماذا Isar ؟

  • 🚀 سهولة اضافتها ولا تحتاج الكثير من Configrations
  • ⏱ حفظ بيانات ضخمة NoSQL والاستعلام منها بسهولة.
  • 🕵️ Full-text search وهذا يُعتبر من اهم المميزات.
  • 🧪 ACID : نموذج ACID لتصميم قاعدة البيانات هو واحد من أقدم وأهم مفاهيم نظرية قواعد البيانات. وهو يحدد أربعة أهداف يجب على كل نظام لإدارة قواعد البيانات أن يسعى جاهداً لتحقيقها: الذرية والاتساق والعزلة والمتانة. لا يمكن اعتبار قاعدة البيانات العلائقية التي تفشل في تلبية أي من هذه الأهداف الأربعة موثوقًا بها. تعتبر قاعدة البيانات التي تمتلك هذه الخصائص متوافقة مع ACID.
  • والمزيد من المميزات.

💻 مشروع جديد:

انشئ مشروع جديد Flutter وقم بإضافة هذه الحُزم في pubspec.yaml.

isar_version: &isar_version 3.0.2 # define the version to be used

dependencies:
  isar: *isar_version
  isar_flutter_libs: *isar_version # contains Isar Core

dev_dependencies:
  isar_generator: *isar_version
  build_runner: any

📄 Annotate الكائن:

يمكن القول عنها أنها تعليق توضيحي للكائن تبدأ بـ@ وبهدها يكون التعليق. سنقوم بانشاء كائن “Class” باسم User ونقوم باضافة هذا التعليق التوضيحي @collection.

part 'user.g.dart';

@collection
class User {
  Id id = Isar.autoIncrement; // you can also use id = null to auto increment

  String? name;

  int? age;
}

🤖 توليد الكود:

اذا كنت تعرف Flutter ولديك خبرة قريبة الى المتوسطة فقط سمعت عن build_runner الذي يقوم بتوليد الكود تلقائيا من خلال التسميات التوضيحية او غيرها من التسميات. سيقوم الأمر التالي بإنشاء ملف جديد بإسم user.g.dart في ملفات المشروع.

⚠️ تنبيه :

لا تقم بتعديل اي ملف يحتوي على g، لأن التعديلات هنا ستمحى بمجر تشغيل أمر البناء او التوليد مرة أخرى.

flutter pub run build_runner build #

🆕 انشاء Isar:

سنقوم بإنشاء Isra وسنقوم بتمرير الـCollection الذي قمنا بإنشاءه سابقًا. بشكل اختياري يمكنك تمرير الـCollection والمسار ايضًا.

final isar = await Isar.open([UserSchema]);

💽 القراء والكتابة في Isar:

الآن بعد إنشاء Isar سنقوم بالقيام بعمليتين بسيطة في قواعد البيانات وهي الإضافة والقراءة. بالإضافة ان جميع عمليات CURD متوفرة في IsarCollection والذي هو كائن User.

💡 CURD :

تعني عمليات قواعد البيانات انشاء من Create و تعديل من Update واسترجاع من Read وايضًا حذف من Delete

final newUser = User()..name = 'Jane Doe'..age = 36;

await isar.writeTxn(() async {
  await isar.users.put(newUser); // insert & update
});

final existingUser = await isar.users.get(newUser.id); // get

await isar.writeTxn(() async {
  await isar.users.delete(existingUser.id!); // delete
});

🕵️‍♀️ معاينة قاعدة البيانات:

الجميل في Isar دعمها للـInspector لقاعدة البيانات مع بعض خصائص الإستعلامات البسيطة. اذا قمت بتشغيل التطبيق ستظهر لكـ هذه الرسالة في Debug Console

اتبع الرابط للذهاب الى المتصفح

بعد الضغط على الرابط سيفتح المتصفح على صفحة تحتوي على جميع Collections وايضًا بعض خيارات الفلتره والاستعلامات بالاضافة ايضًا للتعديل.


الآن بعد الإطلاع على اساسيات القاعدة سنقوم بعمل تطبيق ملاحظات، سنتعرف من خلاله على التعامل مع انواع البيانات مثل البيانات المنطقية التواريخ النصوص والأرقام، ايضًا طريقة الإستعلام والفترة بالإضافة للأهم وهو التعامل مع العلاقات “Links”.

🗄 انشاء Collectios:

سنقوم بإنشاء كائنين يمثلون جداول قواعد البيانات “Collection”، كائن يعبر عن تصنيف الملاحظة وكائن يعبر عن الملاحظة بنفسها.

حُزمة Isar تدعم جميع أنواع البيانات الموجودة في الإسفل:

  • bool
  • int
  • double
  • DateTime
  • String
  • List<bool>
  • List<int>
  • List<double>
  • List<DateTime>
  • List<String>

ايضًا دعمها للـEnum وأنواع أخرى. سيكون شكل الكائنات في تطبيقنا بهذا الشكل:

في Isar يتم التعامل مع العلاقات على مبدأ التضمين embeded.

import 'package:isar/isar.dart';

part 'category.g.dart';

@collection
class Category {
  Id id = Isar.autoIncrement;

  String? name;

  String? description;
}

كائن الملاحظات:


import 'package:isar/isar.dart';

part 'note.g.dart';

@collection
class Note {
  Id id = Isar.autoIncrement;
  String? title;
  String? content;
  int? color;
  DateTime? created;
  DateTime? updated;
  int? pinned;
  int? archived;
}

🔗 Links العلاقات:

يسمح Link بانشاء علاقات بين Collections مثل المعمول بها في SQL، مثال الكاتب له مقالات والمقالات لها تعليقات وبعض التعليقات لها ردود او مناقشة. هذه جميعها علاقات. وتتصنف العلاقات 1:1 و 1:n او n:n.

في مثال تطبيق الملاحظات نحتاج ان يكون كائن Note مرتبط بـCategory، لأن كل ملاحظة لها تصنيف واحد او ربما أكثر من تصنيف. Isar يقدم لنا طريقة سريعة لإضافة العلاقات.

سنقوم باضافة سطر جديد الى كائن Note وسيكون بهذا الشكل

import 'package:isar/isar.dart';

import 'category.dart';

part 'note.g.dart';

@collection
class Note {
  Id id = Isar.autoIncrement;
  String? title;
  String? content;
  int? color;
  DateTime? created;
  DateTime? updated;
  int? pinned;
  int? archived;
  final category = IsarLink<Category>();
}

نلاحظ IsarLink يقوم بربط Category الى Note، هنا العلاقة one-to، الاضافة تكون lazy ، سنحتاج الى التوضيح الى Links اننا نريد القيام بإضافة قسم. المميز هو ان Links سيقوم باضافتها مباشرة.

📒 تجربة اضافة ملاحظة واسناد قسم لها:
// create new category
    final category = Category()
      ..name = 'Category 1'
      ..description = 'Category 1 description';
    // create new note
    final note = Note()
      ..title = 'Note 1'
      ..content = 'Note 1 content'
      ..category.value = category;
    await isar.writeTxn(() async {
      await isar.notes.put(note);
      await isar.categorys.put(category);
      await note.category.save();
    });

قمنا. بإنشاء تصنيف جديد وملاحظة جديدة، بعد ذلك قمنا بإسناد التصنيف للملاحظة الجديدة وقمنا بحفظها في قاعدة البيانات! هل هناك طريقة أخرى للحفظ؟ نعم نستطيع استخدام `synchronous` وسيكون الكود بالشكل التالي

  // create new category
    final category = Category()
      ..name = 'Category 2'
      ..description = 'Category 2 description';
    // create new note
    final note = Note()
      ..title = 'Note 2'
      ..content = 'Note 2 content'
      ..category.value = category;

    isar.writeTxnSync(() {
      isar.notes.putSync(note);
    });

لكن ماذا لو أردنا ان تكون الملاحظة تحتوي على العديد من Categories ؟ سنقوم باستخدام IsarLinks ، هذا يعني ان هذه الملاحظة تحتوي على العديد من التصنيفات. سنقوم بتعديل الإضافة لتكون بالشكل التالي:

  // create new category
    final category = Category()
      ..name = 'Category 1'
      ..description = 'Category 1 description';
    final category2 = Category()
      ..name = 'Category 2'
      ..description = 'Category 2 description';
    // create new note
    final note = Note()
      ..title = 'Note 2'
      ..content = 'Note 2 content'
      ..created = DateTime.now()
      ..updated = DateTime.now()
      ..pinned = 0
      ..archived = 0
      ..category.addAll([category, category2]);

    isar.writeTxnSync(() {
      isar.notes.putSync(note);
    });

نلاحظ انه تم إسناد أكثر من تصنيف لنفس الملاحظة، عند تشغيل التطبيق ومشاهدة التغييرات في Isar Inspector ستلاحظ التالي:

تلاحظ هناك أكثر من تصنيف تم إسناده الى الملاحظة، في السابق كان لكل ملاحظة تصنيف واحد فقط. أما الآن فلدينا العديد من التصنيفات لنفس الملاحظة.

Backlinks

حُزمة Isar تدعم العلاقات العكسية. فاذا مثلا اردنا معرفة كل ملاحظة موجودة في كل تصنيف سنقوم باضافة Backlinks الى Collection التصنيفات.

الآن اصبح لديك معرفة عن طريقة العلاقات والعلاقات العكسية، ايضًا كيف نستطيع استخدام انواع مختلفة من البيانات في Collections بالاضافة الى طريقة الاضافة والقراءة. المهم الآن هو طريقة الاستعلامات؟ كيف نستطيع استرجاع قيم معينة بإستعلامات محددة.

🗃 الإستعلامات والفلترة:

أهم خطوة بعد انشاد العلاقات هي الإستعلامات. كيف استخرج معلومة من قاعدة البيانات. سواء على قيمة معينة تاريخ او أيضًا على جزء معين من نص. سنقوم بإضافة عدة ملاحظات وسنجري عليها بعض الإستعلامات.

الفلترة:

تُعتبر الفلترة أسهل مايمكن القيام به في قاعدة البيانات، لأن غالبا مايكون على حقل معين. مثلًا فلتر البيانات بحيث تكون جميع المخرجات للأشخاص اعمارة اكبر من 32 او يساوي 32 الخ.

الحالةالوصف
.equalTo(value)جميع الحقول المساوية لهذه القيمة بالضبط
.between(lower, upper)جميع الحقول بين قيمتين مختلفة
.greaterThan(bound)اي قيمة أكقر من القيمة المُعطاه
.lessThan(bound)هنا جميع القيم اقل من.
القيمة المفقودة Null سيتم احتسابها على اساس انها قيم صغيرة.
.isNull()الحقول ذات القيم المفقودة
.isNotNull()الحقول التي لا تحوي قيم مفقودة
.length()طول النص او القائمة الراجعة
جميع العمليات في الفلترة.

اذا لاحظنا في الملاحظات لدينا، يوجد تاريخ نهاية الملاحظة. سنقوم بانشاء فلتر يقوم بإعادة الملاحظات التي مضت، من خلال الجدول السابق سنقوم باستخدام lessThan التاريخ الحالي.

   final notes = await isar.notes
        .filter()
        .deadlineLessThan(
          DateTime.now(),
        )
        .findAll();
    print('notes less than today: ${notes.length}');

من خلال الإستعلام في الأعلى نُلاحظ اننا قمنا باستخدام Filter واضح للقراءة. اعطني جميع الملاحظات التي عدت قبل هذا التاريخ. استخدمنا حقل deadline وكانت النتيجة:

لدينا 15 ملاحظة مضى عليها الوقت

قيس على ذلك بقية خيارات الفلتر.

العمليات المنطقية:

حزمة Isar تأتي بمميزات خارج الصندوق بحيث تستطيع الدمج بين حقلين وفلترتها على أكثر من حالة.

العمليةالوصف
.and()اذا كانت القيم صحيحة في جميع الحقول المعطاه.
.or()اذا كانت قيمة أحدهم صحيحة
.xor()اذا كانت اي قيمة صحيحة
.not()عكس العملية
.group()انشاء مجموعة من العمليات المنطقية
جدول العمليات المنطقية.

العمليات المنطقية:
اذا تذكرت البوابات المنطقية من خلال دراستك فهذا مشابه له في جميع الحالات، يمكنك الإستعانة بكتاب اول ثانوي – مسارات الفصل الدراسي الأول للإستفادة.

الآن سنقوم بتحديد فلتر لملاحظاتنا، اذا كانت قبل تاريخ اليوم وتمت أرشفتها باستخدام حقلين deadline و archive.

final twoCond = await isar.notes
        .filter()
        .deadlineLessThan(
          DateTime.now(),
        )
        .and()
        .archivedGreaterThan(0)
        .findAll();
    print('notes less than today and archived : ${twoCond.length}');

عند تشغيل هذا الكود سيقوم بالتحقق من الحقلين اذا جميعهم true ، بما ان البيانات تم توليدها بشكل آلي وعشوائي كانت النتيجة صفر. لكن الفكرة وصلت بإذن الله،

استعلامات النصوص:

أحد اهم الإستعلامات، هو الإستعلام عن نص معين، لدينا العديد من الخيارات للإستعلام باستخدام النصوص، سنلاحظ في الجدول التالي بعض العمليات.

الحالةالوصف
.startsWith(value)كل النصوص التي تبدأ بـ
.contains(value)كل النصوص التي تحتوي على كلمة مُعينة
.endsWith(value)كل النصوص التي تنتهي بـ
.matches(wildcard)استخدام wildcard وهو شبيه بـRegex
جدول عمليات النصوص

جميع عمليات النصوص لديها خاصية هل تريد أن يكون النص بالضبط مثل اللغات الإنجليزية حروف كبيرة وصغيرة.

WildCard

اذا قمنا باستخدام علامة ? فهذا يعني ان علامة الرستفهام تساوي اي حرف، فهثلا c?t تكون اي كلمة cat, cut والخ.

Wildcard character
 initDataBase() async {
    final isar = await Isar.open(
      [CategorySchema, NoteSchema],
    );

    // create new category
    final category = Category()
      ..name = 'Category 1'
      ..description = 'Category 1 description';
    final category2 = Category()
      ..name = 'Category 2'
      ..description = 'Category 2 description';
    // use faker to generate random data
    final faker = Faker();
    // create empty list of notes
    isar.writeTxn(() async {
      for (var i = 0; i < 100; i++) {
        final note = Note()
          ..title = faker.lorem.sentence()
          ..content = faker.lorem.sentences(5).join(' ').trim()
          ..archived = faker.randomGenerator.integer(1)
          ..pinned = faker.randomGenerator.integer(1)
          ..created = faker.date.dateTime()
          ..updated = faker.date.dateTime()
          ..deadline = faker.date.dateTime()
          ..category.add(category);
        isar.notes.put(note);
      }
    });

    // get note less than date today
    final notes = await isar.notes
        .filter()
        .deadlineLessThan(
          DateTime.now(),
        )
        .findAll();
    print('notes less than today: ${notes.length}');
    final twoCond = await isar.notes
        .filter()
        .deadlineLessThan(
          DateTime.now(),
        )
        .and()
        .archivedGreaterThan(0)
        .findAll();
    print('notes less than today and archived : ${twoCond.length}');
  }

😎 الخاتمة

يقدم لنا Isar اسهل وأفضل الطرق للتعامل مع قواعد البيانات، اضافة وحذف وتعديل وقراءة. ايضا مرونة وقابلية للتوسع والتعديل. هناك العديد من المميزات مثل الفلترة والعمليات المنطقية وعمليات النصوص الخاصة. بالإضافة الى البحث الكامل عن النصوص وايضًا الاستعلامات المُخصصة والمدموجة التي سنجعلها بإذن الله في تدوينة قادمة. اتمنى اني وفقت فيما قدمت.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *