عنوان:

‫ساخت دیتابیس sqlite با EF6 Code First


نویسنده: علی یگانه مقدم
تاریخ: ۱۳۹۴/۰۸/۰۴ ۲۰:۲۵
آدرس: www.dntips.ir
تا نسخه EF6 و minor‌های آن به دلیل عدم پشتیبانی داریور sqlite از migration، ساخت دیتابیس با code first ممکن نیست برای همین مجبور هستند از پیاده سازی‌های خودشان و موجود بودن دیتابیس از قبل با استفاده از EF با آن کار کنند که یکی از مثال‌های آن در این آدرس قرار دارد و سعی دارد کلاسی مشابه sqlitehelper در اندروید که کار ساخت دیتابیس و مدیریت نسخه را دارد بسازد و از آن استفاده کند. البته در EF7 این مشکل حل شده است و تیم دات نت تمهیداتی را برای آن اندیشیده‌اند. در این نوشتار قصد داریم با استفاده از یک کتابخانه که توسط آقای مارک سالین نوشته شده است کار ساخت دیتابیس را آسانتر کنیم. این کتابخانه که با دات نت 4 به بعد کار میکند خیلی راحت می‌تواند دیتابیس شما را به روش Code First ایجاد کند.

در حال حاضر این کتابخانه از مفاهیم زیر پشتیبانی می‌کند:

  • تبدیل کلاس به جدول با پشتیبانی از خصوصیت Table
  • تبدیل پراپرتی‌ها به ستون با پشتیبانی از خصوصیت هایی چون Column,Key,MaxLength,Required,Notmapped,DatabaseGenerated,Index
  • پشتیبانی از primarykey و کلید‌های ترکیبی
  • کلید خارجی و روابط یک  به چند و پشتیبانی از cascade on delete
  • فیلد غیر نال


برای شروع ابتدا کتابخانه مورد نظر را از Nuget با دستور زیر دریافت کنید:
Install-Package SQLite.CodeFirst
خود این دستور باعث می‌شود که وابستگی‌هایش از قبیل sqlite provider‌ها نیز دریافت گردند.
solution من شامل سه پروژه است یکی برای مدل‌ها که شامل کلاس‌های زیر برای تهیه یک دفترچه تلفن ساده است:

Person
 public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public virtual ICollection<PhoneBook> Numbers { get; set; }

    }

PhoneBook
  public class PhoneBook
    {
        public int Id { get; set; }

        public string Field{ get; set; }

        public string Number { get; set; }

        public virtual Person Person { get; set; }
    }

پروژه بعدی به نام سرویس که جهت پیاده سازی کلاس‌های EF است و دیگری هم یک پروژه‌ی WPF جهت تست برنامه.
در پروژه‌ی سرویس ما یک کلاس به نام Context داریم که مفاهیم مربوط به پیاده سازی Context در آن انجام شده است:
public class Context:DbContext
    {
        public Context():base("constr")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            var initializer = new InitialDb(modelBuilder);
            Database.SetInitializer(initializer);        
        }

        public DbSet<PhoneBook> PhoneBook { get; set; }
        public DbSet<Person> Persons { get; set; }
    }
تا به الان چیز جدیدی نداشتیم و همه چیز طبق روال صورت گرفته است؛ ولی دو نکته‌ی مهم در این کد نهفته است:

 اول اینکه در سطر اول متد بازنویسی شده onModelCreating، قرارداد مربوط به نامگذاری جداول را حذف می‌کنیم چرا که در صورت نبودن این خط، اسامی که کلاس sqllite برای آن در نظر خواهد گرفت با اسامی که برای انجام عملیات CURD استفاده می‌شوند متفاوت خواهد بود. برای مثال برای Person جدولی به اسم People خواهد ساخت ولی برای درج، آن را در جدول Person انجام می‌دهد که به خاطر نبودن جدول با خطای چنین جدولی موجود نیست روبرو می‌شویم.

نکته‌ی دوم اینکه در همین کلاس Context ما یک پیاده سازی جدید بر روی کلاس InitialDb داشته ایم که در زیر نمونه کد آن را می‌بینید:
 public class InitialDb:SQLite.CodeFirst.SqliteCreateDatabaseIfNotExists<Context>
    {
        public InitialDb(DbModelBuilder modelBuilder) : base(modelBuilder)
        {
        }

        protected override void Seed(Context context)
        {
            var person = new Person()
            {
                FirstName = "ali",
                LastName = "yeganeh",
                Numbers = new List<PhoneBook>()
                {
                    new PhoneBook()
                    {
                        Field = "Work",
                        Number = "031551234"
                    },
                    new PhoneBook()
                    {
                        Field = "Mobile",
                        Number = "09123456789"
                    },
                    new PhoneBook()
                    {
                        Field = "Home",
                        Number = "031554321"
                    }
                }
            };

            context.Persons.Add(person);
            base.Seed(context);
        }
    }
در این کد کلاس InitialDb از کلاس SqliteCreateDatabaseIfNotExists ارث بری کرده‌است و متد seed آن را هم بازنویسی کرده‌ایم. کلاس SqliteCreateDatabaseIfNotExists برای زمانی کاربر دارد که اگر دیتابیس موجود نیست آن را ایجاد کند، در غیر اینصورت خیر. به غیر از آن، کلاس دیگری به نام SqliteDropCreateDatabaseAlways هم وجود دارد که با هر بار اجرا، جداول قبلی را حذف و مجددا آن‌ها را ایجاد میکند.
سپس در پروژه‌ی اصلی WPF در فایل AppConfig رشته اتصالی مورد نظر را وارد نمایید:
  <connectionStrings>
    <add name="constr" connectionString="data source=.\phonebook.sqlite;foreign keys=true" providerName="System.Data.SQLite" />
  </connectionStrings>
نکته‌ی مهم اینکه با افزودن کتابخانه از طریق nuget فایل app.config به روز می‌شود؛ ولی به نظر می‌رسد که تنظیمات به درستی انجام نمی‌شوند. در صورتیکه به مشکل زیر برخوردید و نتوانستید برنامه را اجرا کنید، کد زیر را که قسمتی از فایل app.config است، مطالعه فرمایید و موارد مربوط به آن را اصلاح کنید:

خطا:
The ADO.NET provider with invariant name 'System.Data.SQLite' is either not registered in the machine or application config file, or could not be loaded

قسمتی از فایل app.config:
<entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
          <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />

    </providers>
  </entityFramework>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SQLite.EF6" />
      <remove invariant="System.Data.SQLite" />
       <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".Net Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
    </DbProviderFactories>
  </system.data>

کد Load پروژه WPF:
 public MainWindow()
        {
            InitializeComponent();
           var context=new Context();

            var list= context.Persons.ToList();

            var s = "";

            foreach (var person in list)
            {
                s += person.FirstName + " " + person.LastName +
            " has these numbers:" + Environment.NewLine;

                foreach (var number in person.Numbers)
                {
                    s += number.Field + " : " + number.Number + Environment.NewLine;
                }

                s += Environment.NewLine;
            }
           MessageBox.Show(s);


        }



دانلود مثال
 

نظرات

  • موسی بصیرت نیا در ۱۳۹۶/۰۲/۰۴ ۱۱:۴۹
    سلام؛ مثال را دانلود کردم و نتایج به شرح زیر است:
    1- مثال شما کاملا صحیح و درست کار می‌کند ( احتمالا با VS2013 نوشته اید)
    2-با VS2015 سعی کردم پروژه ای مطابق مثال شما بسازم ولی با خطایی که در سایت مشخص کردید برخوردم و برای رفع خطا مطابق دستورات شما رفتار کردم با این حال همچنان با خطا مواجه شدم. خطای Raised شده حاوی عبارت زیر است:
    the underlying provider failed to open
    تلاش‌های بنده تا در این لحظه حل نشده است.
    از شما خواهشمندم یک مثال با VS2015 ایجاد کنید. و در سایت قرار دهید. چرا که وقتی با Nuget  نسخه جدید SQLite.Codefirst و وابسته‌های آن را نصب می‌کنم با نسخه ای که شما از آن در پروژه استفاده کرده اید متفاوت است ( جدید‌تر است)
    • وحید نصیری در ۱۳۹۶/۰۲/۰۴ ۱۲:۲۴
      - با به روز رسانی بسته‌های نیوگت مثال پیوستی EF 6x، مشکلی مشاهده نشد و مثال قابل اجرا است. نگارش ویژوال استودیو در اینجا مهم نیست و تنها اجرای دستور ذیل مهم است:
      PM> update-package
      الف) فایل کانفیگ پروژه خودتان را بررسی کنید و با محتوای فایل App.config پیوستی مطابقت دهید.
      ب) همچنین رشته‌ی اتصالی آن باید به محل دقیق قرارگیری فایل phonebook.sqlite اشاره کند.
      ج) نام بسته‌های موجود در فایل packages.config خودتان را با نمونه‌ی پروژه‌ی پیوستی مطابقت دهید.
      این موارد را بررسی کنید؛ وگرنه EF 6x در حال توسعه‌ی پیوسته نیست و تغییر خاصی از زمان نگارش این مطلب نداشته‌است. 

      + اگر می‌خواهید نسخه‌ی EF Core آن‌را بررسی کنید:
      - نیاز است سری EF Core را در سایت از ابتدا مطالعه کنید. 
      - VS 2015 دیگر برای کار با NET Core‌. مناسب نیست. حتما نیاز است VS 2017 را نصب کنید. اطلاعات بیشتر
      - پیشنیازهای جدید کار با SQLite در فایل csproj آن برای VS 2017 و EF Core 1.1.1 به صورت ذیل هستند:
      <Project Sdk="Microsoft.NET.Sdk.Web">
        <ItemGroup>
          <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
          <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" PrivateAssets="All" />
          <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
          <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
          <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0" PrivateAssets="All" />
        </ItemGroup>
        <ItemGroup>
          <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.*" />
        </ItemGroup>
      </Project>
      - قسمت‌های تنظیم Context آن‌را در مطالب «استفاده از EF7 با پایگاه داده SQLite تحت NET Core. به کمک Visual Studio Code» و «استفاده از پروایدر SQLite در Entity Framework 7» پیگیری کنید.
      • موسی بصیرت نیا در ۱۳۹۶/۰۲/۰۴ ۱۷:۳۶
        سلام مجدد
        من از دات نت Core استفاده نمی‌کنم.
        من مطابق زیر عمل کردم ولی بازم همان خطا را می‌دهد.
         the underlying provider failed to open
        نمیدونم چرا تگ  Connection-string  را نمیشناسه. 
        دقیقا در پروژه شما اگر تگ  Connection-string  را برداریم با چنین خطایی روبرو می‌شویم.
        من پروژه تستی خودم را اینجا آپلود کردم.
        کد XML فایل پروژه
        <?xml version="1.0" encoding="utf-8"?>
        <!--
          For more information on how to configure your ASP.NET application, please visit
          http://go.microsoft.com/fwlink/?LinkId=301880
          -->
        <configuration>
          <configSections>
            <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
            <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
          </configSections>
          <appSettings>
            <add key="webpages:Version" value="3.0.0.0" />
            <add key="webpages:Enabled" value="false" />
            <add key="ClientValidationEnabled" value="true" />
            <add key="UnobtrusiveJavaScriptEnabled" value="true" />
          </appSettings>
          <connectionStrings>
            <add name="constr" connectionString="data source=.\phonebook.sqlite;foreign keys=true" providerName="System.Data.SQLite" />
          </connectionStrings>
          <system.web>
            <compilation debug="true" targetFramework="4.5" />
            <httpRuntime targetFramework="4.5" />
          </system.web>
          <runtime>
            <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
              <dependentAssembly>
                <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
                <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
              </dependentAssembly>
              <dependentAssembly>
                <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
                <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
              </dependentAssembly>
              <dependentAssembly>
                <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
                <bindingRedirect oldVersion="1.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
              </dependentAssembly>
            </assemblyBinding>
          </runtime>
          <system.codedom>
            <compilers>
              <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
              <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
            </compilers>
          </system.codedom>
          <entityFramework>
            <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
              <parameters>
                <parameter value="v13.0" />
              </parameters>
            </defaultConnectionFactory>
            <!--<providers>
              <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
            </providers>-->
            <providers>
              <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
              <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
              <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
            </providers>
          </entityFramework>
          <system.data>
            <DbProviderFactories>
              <remove invariant="System.Data.SQLite.EF6" />
              <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />
              <remove invariant="System.Data.SQLite" />
              <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
            </DbProviderFactories>
          </system.data>
        </configuration>
        کد xml فایل Package
        <?xml version="1.0" encoding="utf-8"?>
        <packages>
          <package id="bootstrap" version="3.0.0" targetFramework="net45" />
          <package id="EntityFramework" version="6.1.3" targetFramework="net45" />
          <package id="jQuery" version="1.10.2" targetFramework="net45" />
          <package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net45" />
          <package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net45" />
          <package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net45" />
          <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.0" targetFramework="net45" />
          <package id="Microsoft.Net.Compilers" version="1.0.0" targetFramework="net45" developmentDependency="true" />
          <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
          <package id="Modernizr" version="2.6.2" targetFramework="net45" />
          <package id="SQLite.CodeFirst" version="1.3.0.17" targetFramework="net45" />
          <package id="System.Data.SQLite" version="1.0.105.0" targetFramework="net45" />
          <package id="System.Data.SQLite.Core" version="1.0.105.0" targetFramework="net45" />
          <package id="System.Data.SQLite.EF6" version="1.0.105.0" targetFramework="net45" />
          <package id="System.Data.SQLite.Linq" version="1.0.105.0" targetFramework="net45" />
        </packages>

        مابقی همون کلاس‌ها و کد‌های مثال آزمایشی شماست.
        من انتظار دارم در لحظه اول در نبود Database.sqlte آن را ایجاد کند. ( مانند مثال شما )

        • وحید نصیری در ۱۳۹۶/۰۲/۰۴ ۱۷:۵۱
          - مطلبی در مورد «LocalDB FAQ» پیشتر منتشر شده‌است. قسمت «ب) ذکر DataDirectory » آن‌را مطالعه کنید.
          - رشته‌ی اتصالی مثال فوق، برای یک برنامه‌ی ویندوزی WPF نوشته شده‌است. این رشته برای یک برنامه‌ی وب (مثال شما) به جایی اشاره می‌کند که پروسه‌ی آن به آن دسترسی نوشتن ندارد.
          - نیاز است پوشه‌ی استاندارد app_data را به برنامه‌ی وب خود اضافه کنید. سپس مسیردهی به آن در رشته‌ی اتصالی به صورت زیر خواهد بود که در آن |DataDirectory| به همان پوشه‌ی app_data اشاره می‌کند:
          <add 
          name="constr" 
          connectionString="data source=|DataDirectory|\phonebook.sqlite;foreign keys=true" 
          providerName="System.Data.SQLite" />
          - کلید constr رشته‌ی اتصالی فوق هم در تعریف Context برنامه، جهت یافتن آن، استفاده شده‌است:
          public Context():base("constr")
          بنابراین به این موضوع هم دقت داشته باشید.