۱۳۹۰/۰۴/۰۴

نگاشت IDictionary در Fluent NHibernate


نگاشت خودكار مجموعه‌ها در Fluent NHibernate ساده است و نياز به تنظيم خاصي ندارد. براي مثال IList به صورت خودكار به Bag ترجمه مي‌شود و الي آخر.
البته شايد سؤال بپرسيد كه اين Bag از كجا آمده؟ كلا 6 نوع مجموعه در NHibernate پشتيباني مي‌شوند كه شامل Array، Primitive-Array ، Bag ، Set ، List و Map هستند؛ اين‌ اسامي هم جهت حفظ سازگاري با جاوا تغيير نكرده‌اند و گرنه معادل‌هاي آن‌ها در دات نت به اين شرح هستند:
Bag=IList
Set=Iesi.Collections.ISet
List=IList
Map=IDictionary

البته در دات نت 4 ، ISet هم به صورت توكار اضافه شده، اما NHibernate از مدت‌ها قبل آن‌را از كتابخانه‌ي Iesi.Collections به عاريت گرفته است. مهم‌ترين تفاوت‌هاي اين مجموعه‌ها هم در پذيرفتن يا عدم پذيرش اعضاي تكراري است. Set و Map اعضاي تكراري نمي‌پذيرند.
در ادامه مي‌خواهيم طرز كار با Map يا همان IDictionary دات نت را بررسي كنيم:

الف) حالتي كه نوع كليد و مقدار (در يك عضو Dictionary تعريف شده)، Entity نيستند
using System.Collections.Generic;

namespace Test1.Model12
{
public class User
{
public virtual int Id { set; get; }
public virtual string Name { get; set; }
public virtual IDictionary<string, string> Preferences { get; set; }
}
}

نحوه تعريف نگاشت كه مبتني است بر مشخص سازي تعاريف كليد و مقدار آن جهت تشكيل يك Map يا همان Dictionary :
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace Test1.Model12
{
public class UserMapping : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany(x => x.Preferences)
.AsMap<string>("FieldKey")
.Element("FieldValue", x => x.Type<string>().Length(500));
}
}
}

خروجي SQL متناظر:
create table "User" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

create table Preferences (
User_id INT not null,
FieldValue NVARCHAR(500) null,
FieldKey NVARCHAR(255) not null,
primary key (User_id, FieldKey)
)

alter table Preferences
add constraint FKD6CB18523B1FD789
foreign key (User_id)
references "User"

ب) حالتي كه مقدار، Entity است
using System.Collections.Generic;

namespace Test1.Model13
{
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IDictionary<string, Property> Properties { get; set; }
}

public class Property
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Value { get; set; }
public virtual User User { get; set; }
}
}

نحوه تعريف نگاشت:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace Test1.Model13
{
public class UserMapping : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany<Property>(x => x.Properties)
.AsMap<string>("FieldKey")
.Component(x => x.Map(c => c.Id));
}
}
}
خروجي SQL متناظر:
create table "Property" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
Value NVARCHAR(255) null,
User_id INT null,
primary key (Id)
)

create table "User" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

create table Properties (
User_id INT not null,
Id INT null,
FieldKey NVARCHAR(255) not null,
primary key (User_id, FieldKey)
)

alter table "Property"
add constraint FKF9F4D85A3B1FD7A2
foreign key (User_id)
references "User"

alter table Properties
add constraint FK63646D853B1FD7A2
foreign key (User_id)
references "User"

ج) حالتي كه كليد، Entity است
using System;
using System.Collections.Generic;

namespace Test1.Model14
{
public class FormData
{
public virtual int Id { get; set; }
public virtual DateTime? DateTime { get; set; }
public virtual IDictionary<FormField, string> FormPropertyValues { get; set; }
}

public class FormField
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
}

نحوه تعريف نگاشت:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace Test1.Model14
{
public class FormDataMapping : IAutoMappingOverride<FormData>
{
public void Override(AutoMapping<FormData> mapping)
{
mapping.Id(x => x.Id);
mapping.HasMany<FormField>(x => x.FormPropertyValues)
.AsEntityMap("FieldId")
.Element("FieldValue", x => x.Type<string>().Length(500))
.Cascade.All();
}
}
}
خروجي SQL متناظر:
create table "FormData" (
Id INT IDENTITY NOT NULL,
DateTime DATETIME null,
primary key (Id)
)

create table FormPropertyValues (
FormData_id INT not null,
FieldValue NVARCHAR(500) null,
FieldId INT not null,
primary key (FormData_id, FieldId)
)

create table "FormField" (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)

alter table FormPropertyValues
add constraint FKB807B9C090849E
foreign key (FormData_id)
references "FormData"

alter table FormPropertyValues
add constraint FKB807B97165898A
foreign key (FieldId)
references "FormField"

يك مثال عملي:
امكانات فوق جهت طراحي قسمت ثبت اطلاعات يك برنامه «فرم ساز» مبتني بر Key-Value بسيار مناسب هستند؛ براي مثال:
برنامه‌اي را در نظر بگيريد كه مي‌تواند تعدادي خدمات داشته باشد كه توسط مدير برنامه قابل اضافه شدن است؛ براي نمونه خدمات درخواست نصب نرم افزار، خدمات درخواست تعويض كارت پرسنلي، خدمات درخواست مساعده، خدمات ... :
public class Service
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<ServiceFormField> Fields { get; set; }
public virtual IList<ServiceFormData> Forms { get; set; }
}

براي هر خدمات بايد بتوان يك فرم طراحي كرد. هر فرم هم از يك سري فيلد خاص آن خدمات تشكيل شده است. براي مثال:
public class ServiceFormField
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual bool IsRequired { get; set; }
public virtual Service Service { get; set; }
}

در اينجا نيازي نيست به ازاي هر فيلد جديد واقعا يك فيلد متناظر به ديتابيس اضافه شود و ساختار آن تغيير كند (برخلاف حالت dynamic components كه پيشتر در مورد آن بحث شد).
اكنون با داشتن يك خدمات و فيلدهاي پوياي آن كه توسط مديربرنامه تعريف شده‌اند، مي‌توان اطلاعات وارد كرد. مهم‌ترين نكته‌ي آن هم IDictionary تعريف شده است كه حاوي ليستي از فيلدها به همراه مقادير وارد شده توسط كاربر خواهد بود:
public class ServiceFormData
{
public virtual int Id { get; set; }
public virtual IDictionary<ServiceFormField, string> FormPropertyValues { get; set; }
public virtual DateTime? DateTime { get; set; }
public virtual Service Service { get; set; }
}

در مورد نحوه نگاشت آن هم در حالت «ج» فوق توضيح داده شد.