.NET Framework 4.5 と Entity Framework コードファーストを利用するアプリでの DB 構築と項目の追加(1)

以前「SQL Server Compact 3.5 を利用するアプリでの DB 構築と項目の追加」で

Visual C# 2010 Express では SQL Server Compact 4.0 は使えないですし、ID の生成を手動で行うのはね。。。ということで、LINQ to SQL を使うことになります(一昨日(6月8日)に Visual Studio Engineering Term のブログに「Visual Studio Express 2012 for Windows Desktop を提供することになった」との記事(Visual Studio Express 2012 for Windows Desktop)が掲載されたので、C# Express でも SQL Server Compact 4.0 が使えるようになるかもしれませんね)。

と書いていて、そのままだったのを思い出したので、Visual Studio Express 2012 for Windows Desktop で Entity Framework コードファーストと Code First Migrations を使って書きなおしてみます。

Entity Framework Code First Migrations については、マイクロソフトの Data Developer Center のコンテンツ(英語)に利用方法が書かれていますが、使うにあたっていろいろ試してみた結果は次のとおりです。

  • Database.SetInitializer に MigrateDatabaseToLatestVersion のインスタンスを渡すことでデータベースの「自動アップデート」を行うことができる
  • マイグレーションの種類は「スクリプト指定」と「オートマチック」の二種類
  • オートマチックが有効になっていなくても Database.SetInitializer に MigrateDatabaseToLatestVersion のインスタンスを渡せば、「自動アップデート」が行われる
  • Configurations.cs のコンストラクタに書かれている 「AutomaticMigrationsEnabled = false;」を「true」にすることでオートマチックなマイグレーションが有効化される
  • オートマチックとなるのは、フィールドの追加・属性の変更を行った後に「Add-Migration」コマンドでスクリプトの作成を行わずに Update-Database コマンドを実行あるいは自動アップデートが行われた場合(対象フィールドに「スクリプトが作成されているもの」と「スクリプトが作成されていないもの」の両方があった場合、「スクリプトが作成されていないもの」の追加のみがオートマチックとなる)。
  • 自動生成される Configuration.cs に初期データ投入用の Seed メソッドがある
  • スクリプトの Up メソッドに 記述を追加するなり、SQL を記述することで、フィールド追加時に既存レコードの追加フィールドに対して値の設定などを行うこともできる
  • オートマチックで DB 更新を行うとスクリプトが存在しないことになるため、既存レコードに対する追加フィールドへの値設定などの調整ができないことになる
  • オートマチックで追加したフィールドがある場合、「Update-Database -TargetMigration: スクリプト名」ではダウングレードできない。ダウングレードを行いたい場合にはコマンドに「-Force」オプションを指定する必要がある
  • Update-Database コマンドでオートマチック・マイグレーションが行われた時点へのダウングレードは、__MigrationHistory テーブル(コードファーストで自動生成される)の MigrationId フィールドの値(例: 201306101155336_AutomaticMigration)を指定することで行うことができる
  • オートマチックで追加したフィールドは「Add-Migration」コマンドを実行しても追加対象のフィールドにならない。オートマチックで追加したフィールドを対象としたスクリプトを生成したい場合には、一度「Update-Database -TargetMigration: スクリプト名 -Fouce」でオートマチックでフィールドを追加する直前の状態に戻した後に、「Add-Migration」コマンドを実行する必要がある
  • リリース(デプロイ)後に、実稼働環境にあるテーブルのフィールドを削除することはできない(SQL を発行すればできるが、そのような運用はやめておいたほうが無難)

また、上記ドキュメントの記述と異なっている点がありました。最初の時点に戻したいときに Update-Database コマンドの -TargetMigration: 引数に渡す文字列は「$InitialDatabase」ではなく「InitialCreate」と指定しないとダメです。

それでは、前回と同様 WPF なプログラムを作っていきます。
プロジェクトを作成し(ProductVupWithSdfTest004 としました)、NuGet で Entity Framework をプロジェクトに導入します(ソリューションエクスプローラーでプロジェクトを右クリックして「NuGet パッケージの管理」を選択して Entity Framework をインストール)。

まずはモデルから。テーブルは3つです。前回作成したデータ構造のバージョン管理用の Manages テーブルは今回必要がないので作成しません。
プロジェクトに Models フォルダを作成し、Models フォルダに Customer クラスを作ります。

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ProductVupWithSdfTest004.Models
{
    public class Customer
    {
        public int CustomerId { get; set; }

        [MaxLength(50)]
        public string CustomerName { get; set; }

        public virtual List<Sale> Sales { get; set; }
    }
}

次に Models フォルダに Product クラスを作ります。

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ProductVupWithSdfTest004.Models
{
    public class Product
    {
        public int ProductId { get; set; }

        [MaxLength(50)]
        public string ProductName { get; set; }

        public virtual List<Sale> Sales { get; set; }
    }
}

次に Models フォルダに Sale クラスを作ります。

namespace ProductVupWithSdfTest004.Models
{
    public class Sale
    {
        public int SaleId { get; set; }
        public int Quantity { get; set; }

        public virtual Customer Customer { get; set; }
        public virtual Product Product { get; set; }
    }
}

次に DbContext です。プロジェクトに DAL フォルダを作成し、DAL フォルダに CreateModifyContext クラスを作ります。

using System.Data.Entity;

using ProductVupWithSdfTest004.Models;

namespace ProductVupWithSdfTest004.DAL
{
    class CreateModifyContext : DbContext
    {
        public CreateModifyContext() : base("SampleModifyDb") { }

        public DbSet<Customer> Customers { get; set; }
        public DbSet<Product> Products { get; set; }
        public DbSet<Sale> Sales { get; set; }
    }
}

次にデータベース接続文字列を設定します。 App.config に connectionStrings 要素を追加し、SampleModifyDb という名前で設定しています(この名前の参照の設定は CreateModifyContext クラスで行なっています)。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
~省略~
  </configSections>
  <connectionStrings>
    <add name="SampleModifyDb" connectionString="Data Source=|DataDirectory|\SampleModifyDatabase.sdf" providerName="System.Data.SqlServerCe.4.0"/>
  </connectionStrings>
~省略~
</configuration>

以前は SqlServerCe の使用を指定する必要がありましたが、現在はフレームワーク側で適切に選択してくれるようになったので、接続文字列の設定だけで済むようになっています。

DB へのアクセスということで、ついでにリポジトリパターンを実装します。
まずは ISalesRepository インターフェイスです。

using System;
using System.Collections.Generic;

using ProductVupWithSdfTest004.Models;

namespace ProductVupWithSdfTest004.DAL
{
    interface ISalesRepository : IDisposable
    {
        List<Customer> FindCustomers();
        Customer GetCustomer(int id);
        Customer AddCustomer(Customer customer);
        void DeleteCustomer(int id);
        void UpdateCustomer(Customer newCustomer);

        List<Product> FindProducts();
        Product GetProduct(int id);
        Product AddProduct(Product product);
        void DeleteProduct(int id);
        void UpdateProduct(Product newProduct);

        List<Sale> FindSales();
        Sale GetSale(int id);
        Sale AddSale(Sale sale);
        void DeleteSale(int id);
        void UpdateSale(Sale newSale);

        void Save();
    }
}

次に SalesRepository クラスですが、今回必要なところのみ実装しています 😛

using System;
using System.Collections.Generic;
using System.Linq;

using ProductVupWithSdfTest004.Models;

namespace ProductVupWithSdfTest004.DAL
{
    class SalesRepository : ISalesRepository
    {
        private readonly CreateModifyContext _context;

        public SalesRepository()
        {
            _context = new CreateModifyContext();
        }

        public List<Customer> FindCustomers()
        {
            return _context.Customers.ToList();
        }

        public Customer GetCustomer(int id)
        {
            throw new NotImplementedException();
        }

        public Customer AddCustomer(Customer customer)
        {
            return _context.Customers.Add(customer);
        }

        public void DeleteCustomer(int id)
        {
            throw new NotImplementedException();
        }

        public void UpdateCustomer(Customer newCustomer)
        {
            throw new NotImplementedException();
        }

        public List<Product> FindProducts()
        {
            throw new NotImplementedException();
        }

        public Product GetProduct(int id)
        {
            throw new NotImplementedException();
        }

        public Product AddProduct(Product product)
        {
            return _context.Products.Add(product);
        }

        public void DeleteProduct(int id)
        {
            throw new NotImplementedException();
        }

        public void UpdateProduct(Product newProduct)
        {
            throw new NotImplementedException();
        }

        public List<Sale> FindSales()
        {
            return _context.Sales.ToList();
        }

        public Sale GetSale(int id)
        {
            throw new NotImplementedException();
        }

        public Sale AddSale(Sale sale)
        {
            return _context.Sales.Add(sale);
        }

        public void DeleteSale(int id)
        {
            throw new NotImplementedException();
        }

        public void UpdateSale(Sale newSale)
        {
            throw new NotImplementedException();
        }

        public void Save()
        {
            _context.SaveChanges();
        }

        #region IDisposable

        private bool _disposed = false;

        /// <summary>
        /// リソースの開放を行います。
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// リソースの開放を行います。
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed) return;

            _disposed = true;

            if (disposing)
            {
                // マネージ リソースの解放処理
            }
            // アンマネージ リソースの解放処理
            _context.Dispose();
        }

        #endregion

        /// <summary>
        /// デストラクタ
        /// </summary>
        ~SalesRepository()
        {
            Dispose(false);
        }
    }
}

ここまでで DB 関連の設定は終わったので、次は画面です。「SQL Server Compact 3.5 を利用するアプリでの DB 構築と項目の追加」と同じにはできないので、ボタンは「データ追加」「顧客情報取得」「販売記録取得」とします。
MainWindow.xaml

<Window x:Class="ProductVupWithSdfTest004.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:ProductVupWithSdfTest004.ViewModels"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>
    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Margin" Value="5" />
                <Setter Property="Padding" Value="10 5" />
            </Style>
        </StackPanel.Resources>
        <TextBlock
            Text="SQL Server Compact の初期設定及び追加設定のテスト"
            FontSize="14" Margin="10 10 10 5" HorizontalAlignment="Center" />
        <TextBlock
            Text="(その4・Entity Framework を使用)"
            FontSize="14" Margin="10 5 10 10" HorizontalAlignment="Center" />
        <Button
            Content="データ追加" Command="{Binding AddCommand}" />
        <Button
            Content="顧客情報取得" Command="{Binding FindCustomersCommand}" />
        <Button
            Content="販売記録取得" Command="{Binding FindSalesCommand}" />
        <Border
            BorderBrush="Black" BorderThickness="1" CornerRadius="15" Padding="10">
            <TextBlock FontSize="11" Text="{Binding Result}" />
        </Border>
    </StackPanel>
</Window>

次に ViewModel です。プロジェクトに ViewModels フォルダを作ります。
MVVM パターンなので、お決まりのコマンド処理とプロパティチェンジイベントの発火を提供する ViewModelBase クラスは別記事の ViewModelBase で書いたものを使っています。別プロジェクトにして作成した DLL を参照設定で追加するなり、プロジェクト内にソースを取り入れるなりしてください(次のコードは DLL を参照する前提で書いています)。
ViewModels フォルダに MainWindowViewModel クラスを作ります。

using System.Text;
using System.Windows.Input;
using MakCraft.ViewModels;
using ProductVupWithSdfTest004.DAL;
using ProductVupWithSdfTest004.Models;
namespace ProductVupWithSdfTest004.ViewModels
{
    class MainWindowViewModel : ViewModelBase
    {
        private string _result;
        public string Result
        {
            get { return _result; }
            set
            {
                _result = value;
                base.RaisePropertyChanged(() => Result);
            }
        }
        private void addCommandExecute()
        {
            using (var repository = new SalesRepository())
            {
                addCommandExecute(repository);
            }
        }
        private void addCommandExecute(ISalesRepository repository)
        {
            var customer = repository.AddCustomer(new Customer { CustomerName = "カスタマー1" });
            var product = repository.AddProduct(new Product { ProductName = "商品1" });
            repository.AddSale(new Sale { Customer = customer, Product = product, Quantity = 10 });
            repository.Save();
        }
        private ICommand addCommand;
        public ICommand AddCommand
        {
            get
            {
                if (addCommand == null)
                    addCommand = new RelayCommand(
                    param => addCommandExecute()
                    );
                return addCommand;
            }
        }
        private void findCustomersCommandExecute()
        {
            using (var repository = new SalesRepository())
            {
                findCustomersCommandExecute(repository);
            }
        }
        private void findCustomersCommandExecute(ISalesRepository repository)
        {
            var customers = repository.FindCustomers();
            var builder = new StringBuilder();
            customers.ForEach(w => builder.Append(
            string.Format("ID: {0}, Customer: {1}\r\n",
            w.CustomerId, w.CustomerName)));
            Result = builder.ToString();
        }
        private ICommand findCustomersCommand;
        public ICommand FindCustomersCommand
        {
            get
            {
                if (findCustomersCommand == null)
                    findCustomersCommand = new RelayCommand(
                    param => findCustomersCommandExecute()
                    );
                return findCustomersCommand;
            }
        }
        private void findSalesCommandExecute()
        {
            using (var repository = new SalesRepository())
            {
                findSalesCommandExecute(repository);
            }
        }
        private void findSalesCommandExecute(ISalesRepository repository)
        {
            var sales = repository.FindSales();
            var builder = new StringBuilder();
            sales.ForEach(sale => builder.Append(
            string.Format("ID: {0}, Customer: {1}, Product: {2}, Quantity: {3}\r\n",
            sale.SaleId, sale.Customer.CustomerName, sale.Product.ProductName,
            sale.Quantity)));
            Result = builder.ToString();
        }
        private ICommand findSalesCommand;
        public ICommand FindSalesCommand
        {
            get
            {
                if (findSalesCommand == null)
                    findSalesCommand = new RelayCommand(
                    param => findSalesCommandExecute()
                    );
                return findSalesCommand;
            }
        }
    }
}

ここまでで準備ができたので一度動かして DB を作成します。

長くなったので、(その2)へ続きます。


2 thoughts on “.NET Framework 4.5 と Entity Framework コードファーストを利用するアプリでの DB 構築と項目の追加(1)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です