۱۳۹۱/۰۲/۳۰

EF Code First #15


EF Code first و بانك‌هاي اطلاعاتي متفاوت

در آخرين قسمت از سري EF Code first بد نيست نحوه استفاده از بانك‌هاي اطلاعاتي ديگري را بجز SQL Server نيز بررسي كنيم. در اينجا كلاس‌هاي مدل و كدهاي مورد استفاده نيز همانند قسمت 14 است و تنها به ذكر تفاوت‌ها و نكات مرتبط اكتفاء خواهد شد.


حالت كلي پشتيباني از بانك‌هاي اطلاعاتي مختلف توسط EF Code first

EF Code first با كليه پروايدرهاي تهيه شده براي ADO.NET 3.5 كه پشتيباني از EF را لحاظ كرده باشند،‌ به خوبي كار مي‌كند. پروايدرهاي مخصوص ADO.NET 4.0، تنها سه گزينه DeleteDatabase/CreateDatabase/DatabaseExists را نسبت به نگارش قبلي بيشتر دارند و EF Code first ويژگي‌هاي بيشتري را طلب نمي‌كند.
بنابراين اگر حين استفاده از پروايدر ADO.NET مخصوص بانك اطلاعاتي خاصي با پيغام «CreateDatabase is not supported by the provider» مواجه شديد، به اين معنا است كه اين پروايدر براي دات نت 4 به روز نشده است. اما به اين معنا نيست كه با EF Code first كار نمي‌كند. فقط بايد يك ديتابيس خالي از پيش تهيه شده را به برنامه معرفي كنيد تا مباحث Database Migrations به خوبي كار كنند؛ يا اينكه كلا مي‌توانيد Database Migrations را خاموش كرده (متد Database.SetInitializer را با پارامتر نال فراخواني كنيد) و فيلدها و جداول را دستي ايجاد كنيد.


استفاده از EF Code first با SQLite

براي استفاده از SQLite در دات نت ابتدا نياز به پروايدر ADO.NET آن است: «مكان دريافت درايور‌هاي جديد SQLite مخصوص دات نت»
ضمن اينكه به نكته «استفاده از اسمبلي‌هاي دات نت 2 در يك پروژه دات نت 4» نيز بايد دقت داشت.
و يكي از بهترين management studio هايي كه براي آن تهيه شده: «SQLite Manager»
پس از دريافت پروايدر آن، ارجاعي را به اسمبلي System.Data.SQLite.dll به برنامه اضافه كنيد.
سپس فايل كانفيگ برنامه را به نحو زير تغيير دهيد:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </configSections>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>

  <connectionStrings>
    <clear/>
    <add name="Sample09Context"         
         connectionString="Data Source=CodeFirst.db"
         providerName="System.Data.SQLite"/>
  </connectionStrings>
</configuration>

همانطور كه ملاحظه مي‌كنيد، تفاوت آن با قبل، تغيير connectionString و providerName است.
اكنون اگر همان برنامه قسمت قبل را اجرا كنيم به خطاي زير برخواهيم خورد:
«The given key was not present in the dictionary»
در اين مورد هم توضيح داده شد. سه گزينه DeleteDatabase/CreateDatabase/DatabaseExists در پروايدر جاري SQLite براي دات نت وجود ندارد. به همين جهت نياز است فايل «CodeFirst.db» ذكر شده در كانكشن استرينگ را ابتدا دستي درست كرد.
براي مثال از افزونه SQLite Manager استفاده كنيد. ابتدا يك بانك اطلاعاتي خالي را درست كرده و سپس دستورات زير را بر روي بانك اطلاعاتي اجرا كنيد تا دو جدول خالي را ايجاد كند (در برگه Execute sql افزونه SQLite Manager):

CREATE TABLE [Payees](
 [Id] [integer] PRIMARY KEY AUTOINCREMENT NOT NULL,
 [Name] [text] NULL,
 [CreatedOn] [datetime] NOT NULL,
 [CreatedBy] [text] NULL,
 [ModifiedOn] [datetime] NOT NULL,
 [ModifiedBy] [text] NULL
);

CREATE TABLE [Bills](
 [Id] [integer] PRIMARY KEY AUTOINCREMENT NOT NULL,
 [Amount] [float](18, 2) NOT NULL,
 [Description] [text] NULL,
 [CreatedOn] [datetime] NOT NULL,
 [CreatedBy] [text] NULL,
 [ModifiedOn] [datetime] NOT NULL,
 [ModifiedBy] [text] NULL,
 [Payee_Id] [integer] NULL
);

سپس سطر زير را نيز به ابتداي برنامه اضافه كنيد:

Database.SetInitializer<Sample09Context>(null);

به اين ترتيب database migrations خاموش مي‌شود و اكنون برنامه بدون مشكل كار خواهد كرد.
فقط بايد به يك سري نكات مانند نوع داده‌ها در بانك‌هاي اطلاعاتي مختلف دقت داشت. براي مثال integer در اينجا از نوع Int64 است؛ بنابراين در برنامه نيز بايد به همين ترتيب تعريف شود تا نگاشت‌ها به درستي انجام شوند.

در كل تنها مشكل پروايدر فعلي SQLite عدم پشتيباني از مباحث database migrations است. اين مورد را خاموش كرده و تغييرات ساختار بانك اطلاعاتي را به صورت دستي به بانك اطلاعاتي اعمال كنيد. بدون مشكل كار خواهد كرد.

البته اگر به دنبال پروايدري تجاري با پشتيباني از آخرين نگارش EF Code first هستيد، گزينه زير نيز مهيا است:
http://devart.com/dotconnect/sqlite/
براي مثال اگر علاقمند به استفاده از حالت تشكيل بانك اطلاعاتي SQLite در حافظه هستيد (با رشته اتصالي ويژه Data Source=:memory:;Version=3;New=True;)،‌ فعلا تنها گزينه مهيا استفاده از پروايدر تجاري فوق است؛ زيرا مبحث Database Migrations را به خوبي پشتيباني مي‌كند.



استفاده از EF Code first با SQL Server CE

قبلا در مورد «استفاده از SQL-CE به كمك NHibernate» مطلبي را در اين سايت مطالعه كرده‌ايد. سه مورد اول آن با EF Code first يكي است و تفاوتي نمي‌كند (يك سري بحث عمومي مشترك است). البته با يك تفاوت؛ در اينجا EF Code first قادر است يك بانك اطلاعاتي خالي SQL Server CE را به صورت خودكار ايجاد كند و نيازي نيست تا آن‌را دستي ايجاد كرد. مباحث database migrations و به روز رساني خودكار ساختار بانك اطلاعاتي نيز در اينجا پشتيباني مي‌شود.
براي استفاده از آن ابتدا ارجاعي را به اسمبلي System.Data.SqlServerCe.dll قرار گرفته در مسير Program Files\Microsoft SQL Server Compact Edition\v4.0\Desktop اضافه كنيد.
سپس رشته اتصالي به بانك اطلاعاتي و providerName را به نحو زير تغيير دهيد:

<connectionStrings>
    <clear/>
    <add name="Sample09Context"
         connectionString="Data Source=mydb.sdf;Password=1234;Encrypt Database=True"
         providerName="System.Data.SqlServerCE.4.0"/>
</connectionStrings>


بدون نياز به هيچگونه تغييري در كدهاي برنامه، همين مقدار تغيير در تنظيمات ابتدايي برنامه براي كار با SQL Server CE كافي است.
ضمنا مشكلي هم با فيلد Identity در آخرين نگارش EF Code first وجود ندارد؛ برخلاف حالت database first آن كه پيشتر اين اجازه را نمي‌داد و خطاي «Server-generated keys and server-generated values are not supported by SQL Server Compact» را ظاهر مي‌كرد.



استفاده از EF Code first با MySQL

براي استفاده از EF Code first با MySQL (نگارش 5 به بعد البته) ابتدا نياز است پروايدر مخصوص ADO.NET آن‌را دريافت كرد: (^)
كه از EF نيز پشتيباني مي‌كند. پس از نصب آن، ارجاعي را به اسمبلي MySql.Data.dll قرار گرفته در مسير Program Files\MySQL\MySQL Connector Net 6.5.4\Assemblies\v4.0 به پروژه اضافه نمائيد.
سپس رشته اتصالي و providerName را به نحو زير تغيير دهيد:

<connectionStrings>
    <clear/>
    <add name="Sample09Context"
         connectionString="Datasource=localhost; Database=testdb2; Uid=root; Pwd=123;"
         providerName="MySql.Data.MySqlClient"/>
</connectionStrings>

<system.data>
    <DbProviderFactories>
      <remove invariant="MySql.Data.MySqlClient"/>
      <add name="MySQL Data Provider"
           invariant="MySql.Data.MySqlClient"
           description=".Net Framework Data Provider for MySQL"
           type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
    </DbProviderFactories>
</system.data>

همانطور كه مشاهده مي‌كنيد در اينجا شماره نگارش دقيق پروايدر مورد استفاده نيز ذكر شده است. براي مثال اگر چندين پروايدر روي سيستم نصب است، با مقدار دهي DbProviderFactories مي‌توان از نگارش مخصوصي استفاده كرد.

با اين تغييرات پس از اجراي برنامه قسمت قبل، به خطاي زير برخواهيم خورد:
The given key was not present in the dictionary

توضيحات اين مورد با قسمت SQLite يكي است؛ به عبارتي نياز است بانك اطلاعاتي testdb را دستي درست كرد. همچنين جداول و فيلدها را نيز بايد دستي ايجاد كرد و database migrations را نيز بايد خاموش كرد (پارامتر Database.SetInitializer را به نال مقدار دهي كنيد).
براي اين منظور يك ديتابيس خالي را ايجاد كرده و سپس دو جدول زير را به آن اضافه كنيد:

CREATE TABLE IF NOT EXISTS `bills` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Amount` float DEFAULT NULL,
  `Description` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
  `CreatedOn` datetime NOT NULL,
  `CreatedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
  `ModifiedOn` datetime NOT NULL,
  `ModifiedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
  `Payee_Id` int(11) NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `payees` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Name` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
  `CreatedOn` datetime NOT NULL,
  `CreatedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
  `ModifiedOn` datetime NOT NULL,
  `ModifiedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci AUTO_INCREMENT=1 ;

پس از اين تغييرات، برنامه بدون مشكل اجرا خواهد شد (ايجاد بانك اطلاعاتي خالي به همراه ايجاد ساختار جداول و خاموش كردن database migrations كه توسط اين پروايدر پشتيباني نمي‌شود).

به علاوه پروايدر تجاري ديگري هم در سايت devart.com براي MySQL و EF Code first مهيا است كه مباحث database migrations را به خوبي مديريت مي‌كند.


مشكل!
اگر به همين نحو برنامه را اجرا كنيم، فيلدهاي يونيكد فارسي ثبت شده در MySQL با «??????? ?? ????» مقدار دهي خواهند شد و تنظيم CHARACTER SET utf8 COLLATE utf8_persian_ci نيز كافي نبوده است (اين مورد با SQLite يا نگارش‌هاي مختلف SQL Server بدون مشكل كار مي‌كند و نياز به تنظيم اضافه‌تري ندارد):

ALTER TABLE `bills` DEFAULT CHARACTER SET utf8 COLLATE utf8_persian_ci

براي رفع اين مشكل توصيه شده است كه CharSet=UTF8 را به رشته اتصالي به بانك اطلاعاتي اضافه كنيم. اما در اين حالت خطاي زير ظاهر مي‌شود:
The provider did not return a ProviderManifestToken string
اين مورد فقط به اشتباه بودن تعاريف رشته اتصالي بر مي‌گردد؛‌ يا عدم پشتيباني از تنظيم اضافه‌اي كه در رشته اتصالي ذكر شده است.
مقدار صحيح آن دقيقا مساوي CHARSET=utf8 است (با همين نگارش و رعايت كوچكي و بزرگي حروف؛ مهم!):

<connectionStrings>
    <clear/>
    <add name="Sample09Context"
         connectionString="Datasource=localhost; Database=testdb; Uid=root; Pwd=123;CHARSET=utf8"
         providerName="MySql.Data.MySqlClient"/>
</connectionStrings>


به اين ترتيب، مشكل ثبت عبارات يونيكد فارسي برطرف مي‌شود (البته جدول هم بهتر است به DEFAULT CHARACTER SET utf8 COLLATE utf8_persian_ci تغيير پيدا كند؛ مطابق دستور Alter ايي كه در بالا ذكر شد).