تطبيق تجريبي واحد زائد واحد لتطوير البرمجيات الشعار
العرض 01

طبقة البيانات
DataAccessLayer

كيف يتحوّل كيان واحد إلى جدول في قاعدة البيانات، بلا SQL يدوي وبلا عمليات ترحيل.

ما دور هذه الطبقة؟

  • كل وصول إلى قاعدة البيانات يمرّ من هنا — ولا تكتب أي طبقة أخرى SQL.
  • ‏ORM صغير ومركَّز من صنعنا: Record + سمات، بلا Entity Framework.
  • تدعم ثلاثة مزوّدين بالكود نفسه تمامًا: SQL Server وMySQL وSQLite.
  • غير متزامنة من طرف إلى طرف — كل نداء لقاعدة البيانات هو await، ولا يُحجب أي Thread.
Record IDataSupplier SqlDataAccess MySqlDataAccess SqliteDataAccess

‏Record — قلب الطبقة

  • لكل كيان ID (مفتاح أساسي) وDescription.
  • ضَع السمة [Column] على خاصية — فتصبح عمودًا في قاعدة البيانات.
  • اسم الجدول = اسم الفئة؛ والمفتاح = الاسم+Id.
  • لا حاجة إلى ALTER يدوي: تُنشئ SetupAsync عند إقلاع التطبيق الجداول الناقصة، وتضيف الأعمدة أو توسّعها وفق الكود.
public class Car : Record
{
    [Column(Size = 300)]
    public override string Description { get; set; }

    [Column] public bool IsAutomatic { get; set; }
    [Column] public double Volume { get; set; }
    [Column] public string Color { get; set; }
    [Column] public DateTime ProductionDate { get; set; }
}

هذا هو كل الكود اللازم كي يُنشأ جدول Car في قاعدة البيانات ويُدار من الشاشة.

CRUD غير متزامن

Car c = new Car { Description = "مازدا 3", Volume = 2.0 };
await c.SaveAsync();                    // INSERT (‏ID جديد) أو UPDATE — تلقائيًا

Car loaded = await Record.GetAsync<Car>(c.ID);      // تحميل حسب المفتاح
List<Car> all = await Record.AllAsync<Car>();       // كل السجلات
int n        = await Record.CountAsync<Car>();      // عدّ

c.Color = "أحمر";
await c.UpdatePropertiesAsync("Color");  // تحديث عمود واحد — وليس Save كاملًا!

await c.DeleteAsync();
  • قاعدة البيت: التحديث النقطي = UpdatePropertiesAsync وليس SaveAsync — تفاديًا للآثار الجانبية التي يجرّها الحفظ الكامل.
  • DateTime.MinValue يعني "لا يوجد تاريخ" — ويُخزَّن بالقيمة NULL.

استعلامات مُصفّاة — AndFilter

AndFilter f = new AndFilter();
f.Add("color", "أحمر");                          // مساواة، مع معامل
f.Add("volume", 1.6, EqalityOperators.GreaterThan);
f.AddBetween("productionDate", from, to);

List<Car> cars = await Record.PartialAsync<Car>(f, "productionDate desc",
                                                pageNumber: 0, pageSize: 20);
int total     = await Record.CountAsync<Car>(f);
  • يبني المُرشِّح جملة WHERE بمعاملات — بلا دمج نصوص وبلا خطر SQL Injection.
  • ويكمّل OrFilter شروط "أو"؛ أما ترقيم الصفحات فيتمّ عبر PagingTemplate الخاص بكل مزوّد.

كيف يعمل الكود نفسه على ثلاث قواعد بيانات؟

  • يُختار المزوّد وفق سلسلة الاتصال: ‏Server= ← ‏MySQL، و‏Data Source=*.db ← ‏SQLite، وفي ما عدا ذلك ← ‏SQL Server.
  • يُكتب الكود بلغة T-SQL؛ وتتولّى ‏FixSql لدى كل مزوّد الترجمة: ‏GETDATE() و‏ISNULL و‏[أقواس] و‏IDENTITY
  • ويحصل ‏SQLite أيضًا على الدوال YEAR/MONTH/DAY المعرَّفة في C#.
  • التطوير محليًا على SQLite، والإنتاج على SQL Server/MySQL — دون تغيير سطر واحد.

أدوات إضافية في الصندوق

  • التشفير:[Column(IsEncrypted = true)] — تُشفَّر القيمة بخوارزمية AES قبل الكتابة وتُفكّ عند القراءة.
  • التسلسل:SerializeType = Json يحفظ كائنًا كاملًا في عمود نصي.
  • الفهارس:CreateIndexAsync("color") — يُنشأ فقط إن لم يكن موجودًا، لدى كل مزوّد.
  • الاستيراد:ImportAsync يتعرّف على السجلات القائمة عبر IsUniqueId ويحدّثها بدل تكرارها.
  • المعاملات:BeginTransactionAsync / CommitTransactionAsync.

باختصار: عرِّف الكيان، وحدِّد الأعمدة — وتتكفّل الطبقة بكل ما تبقّى.