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" });
    }
}

2014年10月27日 星期一

Entity Framework Code First 組織Fluent API 設定

Fluent API可以設定的功能比較多,但每一個欄位就要寫一行,越大的專案設定就會越多
可以透過EntityTypeConfiguration<T>來分類

一般的設定方式
public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("sid")
            .HasColumnType("bigint");
        modelBuilder.Entity<Table1>().Property(x => x.s1)
            .IsRequired()
            .HasMaxLength(10)
            .IsFixedLength()
            .IsUnicode(false);
        modelBuilder.Entity<Table1>().Property(x => x.c1)
            .HasPrecision(18, 2);
        modelBuilder.Entity<Table1>().Property(x => x.t1)
            .IsRowVersion();
    }
}

分類後的設定方式
public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new Table1Configuration());
    }

    private class Table1Configuration : EntityTypeConfiguration<Table1>
    {
        public Table1Configuration()
        {
            this.ToTable("Table1").HasKey(x => x.id);
            this.Property(x => x.id)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
                .HasColumnName("sid")
                .HasColumnType("bigint");
            this.Property(x => x.s1)
                .IsRequired()
                .HasMaxLength(10)
                .IsFixedLength()
                .IsUnicode(false);
            this.Property(x => x.c1)
                .HasPrecision(18, 2);
            this.Property(x => x.t1)
                .IsRowVersion();
        }
    }
}

Entity Framework Code First 對應規則設定

要改變預設對應規則,可以用兩種方式
一種是使用Attributes的方式,例如[key]
一種是覆寫DbContext的OnModelCreating,透過DbModelBuilder使用Fluent API方式來設定

資料表名稱和主索引
[Table("Table1")]
public class Table1
{
    [Key]
    public int id { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
    }
}

自動編號
[Table("Table1")]
public class Table1
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int id { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    }
}
欄位名稱
[Table("Table1")]
public class Table1
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("sid")]
    public int id { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("sid");
    }
}

欄位型態
[Table("Table1")]
public class Table1
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("sid", TypeName = "bigint")]
    public int id { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("sid")
            .HasColumnType("bigint");
    }
}

不可為空(文字)
[Table("Table1")]
public class Table1
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("sid", TypeName = "bigint")]
    public int id { get; set; }

    [Required]
    public string s1 { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("sid")
            .HasColumnType("bigint");
        modelBuilder.Entity<Table1>().Property(x => x.s1)
            .IsRequired();
    }
}

最大長度(文字)
[Table("Table1")]
public class Table1
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("sid", TypeName = "bigint")]
    public int id { get; set; }

    [Required]
    [MaxLength(10)]
    public string s1 { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("sid")
            .HasColumnType("bigint");
        modelBuilder.Entity<Table1>().Property(x => x.s1)
            .IsRequired()
            .HasMaxLength(10);
    }
}

varchar, char(文字)
[Table("Table1")]
public class Table1
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("sid", TypeName = "bigint")]
    public int id { get; set; }

    [Required]
    [MaxLength(10)]
    [Column(TypeName="char")]
    public string s1 { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("sid")
            .HasColumnType("bigint");
        modelBuilder.Entity<Table1>().Property(x => x.s1)
            .IsRequired()
            .HasMaxLength(10)
            .IsFixedLength()
            .IsUnicode(false);
    }
}

精準度(浮點數)
[Table("Table1")]
public class Table1
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("sid", TypeName = "bigint")]
    public int id { get; set; }

    [Required]
    [MaxLength(10)]
    [Column(TypeName="char")]
    public string s1 { get; set; }

    public decimal c1 { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("sid")
            .HasColumnType("bigint");
        modelBuilder.Entity<Table1>().Property(x => x.s1)
            .IsRequired()
            .HasMaxLength(10)
            .IsFixedLength()
            .IsUnicode(false);
        modelBuilder.Entity<Table1>().Property(x => x.c1)
            .HasPrecision(18, 2);
    }
}

時間戳記
[Table("Table1")]
public class Table1
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("sid", TypeName = "bigint")]
    public int id { get; set; }

    [Required]
    [MaxLength(10)]
    [Column(TypeName = "char")]
    public string s1 { get; set; }

    public decimal c1 { get; set; }

    [Timestamp]
    public byte[] t1 { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => x.id);
        modelBuilder.Entity<Table1>().Property(x => x.id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("sid")
            .HasColumnType("bigint");
        modelBuilder.Entity<Table1>().Property(x => x.s1)
            .IsRequired()
            .HasMaxLength(10)
            .IsFixedLength()
            .IsUnicode(false);
        modelBuilder.Entity<Table1>().Property(x => x.c1)
            .HasPrecision(18, 2);
        modelBuilder.Entity<Table1>().Property(x => x.t1)
            .IsRowVersion();
    }
}

複合索引
[Table("Table1")]
public class Table1
{
    [Key]
    [Column(Order = 1)]
    public int id1 { get; set; }

    [Key]
    [Column(Order = 2)]
    public int id2 { get; set; }
}

public class DemoContext : DbContext
{
    public DbSet<Table1> Table1 { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>().ToTable("Table1").HasKey(x => new { x.id1, x.id2 });
    }
}




2014年10月20日 星期一

Entity Framework Code First 預設對應規則


資料庫
預設採用SQLEXPRESS,如果沒有SQLEXPRESS執行個體,會嘗試Localdb
如果有和DbContext同名的連線字串,就會直接使用
也可透過覆寫建構式的時後,傳入指定的連線字串名稱
或是透過DbConnection,在執行階段傳入連線物件

Table
資料表為類別名稱的複數形式,而且schema都為dbo
可以透過System.ComponentModel.DataAnnotations.Schema.Table
或是Fluent API的Entity<T>.ToTable來指定資料表的名稱
也可以覆寫OnModelCreating,移除複數資料表的約定
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

主索引
預設為類別名稱加上Id結尾的屬性,如果資料形態是int,還會加上自動編號
可以透過System.ComponentModel.DataAnnotations.Key
或是Fluent API的Entity<T>.HasKey來指定
如果是複合索引,Attribute方式需再加上Column來指定欄位的順序
Fluent API則是傳入一個匿名型別

外部索引
如果類別之間有一對多的關系,會自動生成Foreign Key
欄位名稱是導覽屬性加上底線再加上對應主索引的欄位名稱
可以透過System.ComponentModel.DataAnnotations.Schema.ForeignKey來指定
或是透過Fluent API的Entity<T>.Hasxxx().Withxxx來指定
xxx可以是Optional(0或多個)、Required(1或多個)、Many(多個)

資料型態
實值型別會是not null
參考型別會是null
nullable型別會是null
可以透過System.ComponentModel.DataAnnotations.Required來設定不可空值
或是透過Fluent API的Entity<T>().IsRequired()來指定不可空值,IsOptional指定可以空值

也可以透過System.ComponentModel.DataAnnotations.Schema.Column來明確指定對應的格式
或是透過Fluent API的Entity<T>().Property().HasColumnType來指定對應的格式
或是IsUnicode來指定是否為Unicode

C# SQL Server nullable
byte tinyint NOT NULL
short smallint NOT NULL
int int NOT NULL
long bigint NOT NULL
float real NOT NULL
double float NOT NULL
decimal decimal(18,2) NOT NULL
string nvarchar(max) NULL
byte[] varbinary(max) NOT NULL
bool bit NOT NULL
datetime datetime NOT NULL

Entity Framework 筆記

[Code First]

1. Entity Framework Code First 預設對應規則

2. Entity Framework Code First 對應規則設定

3. Entity Framework Code First 組織Fluent API 設定

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

5. Entity Framework Code First 一對一關聯設定

6. Entity Framework Code First 一對多關聯設定

7. Entity Framework Code First 多對多關聯設定

8. Entity Framework Code First Complex Type

9. Entity Framework Code First 繼承對應設定

[DbContext]

1. Entity Framework DbContext 新增、修改、刪除、查詢

2. Entity Framework DbContext 延遲加載、貪婪加載、顯性加載

3. Entity Framework DbContext Change Tracker API

4. Entity Framework DbContext Validation API

5. Entity Framework DbContext SqlQuery、ExecuteSqlCommand

參考書籍

Programming Entity Framework: Code First
ISBN:978-1-4493-1294-7

Programming Entity Framework: DbContext
ISBN:978-1-4493-1296-1

Programming Entity Framework, 2nd Edition
ISBN:978-0-596-80726-9

參考資料

2014年10月1日 星期三

ladda-bootstrap 套件

ladda-bootstrap用將spin.js整合bootstrap,並加強功能的一個套件
官網上有很詳細的使用範例
透過NuGet就可以安裝

可以看到套件相依於spin.js


使用方式是在button上,加上ladda-button的css類別,要顯示的文字加上ladda-label的css類別
<button id="btn1" class="btn btn-primary ladda-button" data-style="expand-left">
    <span class="ladda-label">button</span>
</button>

另外透過data-style來指定動畫效果
data-style="expand-left"
data-style="expand-right"
data-style="expand-up"
data-style="expand-down"
data-style="zoom-in"
data-style="zoom-out"
data-style="slide-left"
data-style="slide-right"
data-style="slide-up"
data-style="slide-down"
data-style="contract"

最後透過JavaScript來觸發,先用Ladda.create(element)來取得物件
呼叫物件的start來開始動畫
呼叫物件的stop來停止動畫
$(function () {
    $("#btn1").click(function () {
        var obj = Ladda.create(this);
        obj.start();
        setTimeout(function () {
            obj.stop();
        }, 1000);
    });
});

spin.js.2.0.1好像和ladda不太和,最好先降到spin.js 1.3版就好



相關資料
Bootstrap button loading text
http://blog.developer.idv.tw/2014/10/bootstrap-button-loading-text.html

spin.js 套件
http://blog.developer.idv.tw/2014/10/spinjs.html

spin.js 套件

spin.js是一個簡單使用的Loading的動畫,官網上有很清楚的使用範例
首先透過NuGet安裝spin.js套件

網頁中放一個div來轉圈圈,再用一個按鈕來觸發事件
<div id="div1"></div>
<input id="btn1" type="button" value="click" />

spin.js本身是無相依性的,這邊用JQuery只是用來綁定事件
呼叫Spinner物件的spin函式來啟用動畫,呼叫stop來停止動畫
$(function () {
    $("#btn1").click(function () {
        spinner = new Spinner().spin($("#div1")[0]);
        setTimeout(function () {
            spinner.stop();
        }, 1000);
    });
});




相關資料
Bootstrap button loading text
http://blog.developer.idv.tw/2014/10/bootstrap-button-loading-text.html

ladda-bootstrap 套件
http://blog.developer.idv.tw/2014/10/ladda-bootstrap.html

Bootstrap button loading text

Bootstrap的Button上,加上data-loading-text的屬性,可以很簡單地做到clickonce的功能
透過JQuery來設定button的狀態
btn.button("loading")會顯示data-loading-text設定的文字,並且把button設為disabled
btn.button("reset")會恢復button的狀態


相關資料
spin.js 套件
http://blog.developer.idv.tw/2014/10/spinjs.html

ladda-bootstrap 套件
http://blog.developer.idv.tw/2014/10/ladda-bootstrap.html