2014年10月31日 星期五

Entity Framework Code First 資料庫初始化設定

在參考了entityFramework套件之後,設定檔中會自動增加一個EntityFramework的區域設定
其中的defaultConnectionFactory宣告了預設的資料庫位置
不同的版本預設的localdb實體名稱會有不同,這邊的例子是mssqllocaldb
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
        <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
    </configSections>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <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" />
        </providers>
    </entityFramework>
</configuration>

就會自動建立一個包含完整Assembly名稱的資料庫


如果在設定檔的連線區段中,有一個和DbContext同名的設定值,就會改用這邊的連線設定
建立的資料庫也會是連線字串中指定的名稱
<connectionStrings>
    <add name="DemoContext" providerName="System.Data.SqlClient"
            connectionString="Data Source=(localdb)\v11.0;Initial Catalog=Demo;Integrated Security=true;"/>
</connectionStrings>

也可以透過預設建構式指定要使用設定檔中的那個值
<connectionStrings>
    <add name="DemoContext" providerName="System.Data.SqlClient"
            connectionString="Data Source=(localdb)\v11.0;Initial Catalog=Demo;Integrated Security=true;"/>
    <add name="d1" providerName="System.Data.SqlClient"
            connectionString="Data Source=(localdb)\v11.0;Initial Catalog=d1;Integrated Security=true;"/>
</connectionStrings>

public class DemoContext : DbContext
{
    public DemoContext() : base("name=d1")
    {
    }

    public DbSet<Class1> Class1 { get; set; }
}

或是使用DbContext物件時,透過建構式傳入要使用的名稱
<connectionStrings>
    <add name="DemoContext" providerName="System.Data.SqlClient"
            connectionString="Data Source=(localdb)\v11.0;Initial Catalog=Demo;Integrated Security=true;"/>
    <add name="d1" providerName="System.Data.SqlClient"
            connectionString="Data Source=(localdb)\v11.0;Initial Catalog=d1;Integrated Security=true;"/>
    <add name="d2" providerName="System.Data.SqlClient"
                connectionString="Data Source=(localdb)\v11.0;Initial Catalog=d2;Integrated Security=true;"/>
</connectionStrings>

public class DemoContext : DbContext
{
    public DemoContext() : base("name=d1")
    {
    }

    public DemoContext(string name) : base(name)
    {
    }

    public DbSet<Class1> Class1 { get; set; }
}

static void Main(string[] args)
{
    using (DemoContext db = new DemoContext("d2"))
    {
        db.Database.Initialize(true);
    }
}


另一種建構式是傳入DbConnection物件,搭配contextOwnsConnection來決定是否要共用連線
public class DemoContext : DbContext
{
    public DemoContext() : base("name=d1")
    {
    }

    public DemoContext(string name) : base(name)
    {
    }

    public DemoContext(DbConnection connection) : base(connection, contextOwnsConnection: false)
    {
    }

    public DbSet<Class1> Class1 { get; set; }
}

如果共用連線的話,在第一個DbContext執行完後就會釋放連線
執行第二個DbContext物件的時後就會報錯
static void Main(string[] args)
{
    SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["d1"].ConnectionString);

    using (DemoContext db = new DemoContext(conn))
    {
        Console.WriteLine(db.Class1.Count());
    }

    using (DemoContext db = new DemoContext(conn))
    {
        Console.WriteLine(db.Class1.Count());
    }
}

System.Data.Entity.Database.SetInitializer函式可以用來初始化資料庫
System.Data.Entity.CreateDatabaseIfNotExists
只有在資料庫不存在的時後才會建立,這是預設的策略

System.Data.Entity.DropCreateDatabaseIfModelChanges
只有當資料庫結構發生變化的時後,才砍掉重練

System.Data.Entity.DropCreateDatabaseAlways
不管資料庫結構是否有改變,每次都砍掉重練

通常在應用程式的進入點例如Global.asax,Main會設置初始化策略
但是在切換環境的時後,要改變策略就要重新修改編譯部署
所以比較方便的方式,是配置在設定檔中,有兩種設定方式

一種是設定在AppSetting區段,設成Disabled就是禁用資料庫初始化功能
<appSettings>
    <add key="DatabaseInitializerForType ConsoleApplication1.Models.DemoContext, ConsoleApplication1"
            value="System.Data.Entity.DropCreateDatabaseIfModelChanges`1[[ConsoleApplication1.Models.DemoContext, ConsoleApplication1]], EntityFramework" />
    <!--<add key="DatabaseInitializerForType ConsoleApplication1.Models.DemoContext, ConsoleApplication1"
            value="Disabled" />-->
</appSettings>

一種是設定在entityFramework的contextx區段
<entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
        <parameters>
            <parameter value="mssqllocaldb" />
        </parameters>
    </defaultConnectionFactory>
    <contexts>
        <context type="ConsoleApplication1.Models.DemoContext, ConsoleApplication1">
            <databaseInitializer type="System.Data.Entity.DropCreateDatabaseIfModelChanges`1[[ConsoleApplication1.Models.DemoContext, ConsoleApplication1]], EntityFramework" />
        </context>
    </contexts>
    <providers>
        <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
</entityFramework>

最後可以新增一個自訂的策略,並覆寫Seed函式,就能在初始化資料庫的時後,執行自訂的動作,例如新增資料
public class MyInitialize : DropCreateDatabaseIfModelChanges<DemoContext>
{
    protected override void Seed(DemoContext context)
    {
        context.Class1.Add(new Class1 { C1 = "a" });
        context.Class1.Add(new Class1 { C1 = "b" });
        context.Class1.Add(new Class1 { C1 = "c" });
    }
}