Code-First Development ด้วย Entity Framework

แรงจูงใจ …

บทความนี้จะแนะนำขั้นตอนการพัฒนา application โดยแนวคิด Code-First Development กับ EF 4.1 แบบง่าย ซึ่งการพัฒนาด้วยแนวคิด Code-First มีข้อดี ก็คือ

  • พัฒนาโดยไม่ต้องเปิดตัว studio designer หรือไม่ต้องกำหนดไฟล์แมป XML
  • กําหนด model ของคุณโดยเขียนต่อจาก “plain old classes” ได้เลยโดยไม่จำเป็นต้องมี base class ใดๆ
  • ใช้วิธีการ “convention over configuration” วิธีการนี้ทำให้สร้าง database persistence ได้โดยไม่จำเป็นต้อง config ใดๆเลย
  • มีทางอื่นให้ override “convention-based persistence” โดยใช้ fluent code API ที่ปลับแต่งการ mapping กับ persistence ได้ตามใจได้หมด

เครื่องมือ …

  • Visual Studio 2010 ตัวเต็มหรือตัว Express
  • EF 4.1

เริ่มต้นกันเลย …

1. install EF 4.1 ก่อน

2. เริ่ม projects ใหม่เป็น Console Application ตั้งชื่อว่า SimpleCodeFirstDemo ตามภาพ

3. reference lib เข้ามา 2 lib ได้แก่ EntityFramework และ System.Data.Entity ตามภาพ

4. เพิ่ม class domain model เข้าไป 2 class คือ Product และ Category โดย Category บรรจุ Product ได้หลายรายการ

//Product.cs

public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }

public virtual Category Category { get; set; }
}

//Category.cs

public class Category
{
public string CategoryId { get; set; }
public string Name { get; set; }

public virtual ICollection<Product> Products { get; set; }
}

5. เพิ่ม class ProductContext แล้ว using System.Data.Entity และสืบทอดจาก DbContext

public class ProductContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }

}

6. ไปที่ main program ที่ file Program.cs เพื่อทำการ add  food category เข้าไปในระบบ และเป็นการทดสอบ ProductContext ของเราว่า ทำงาน work มั้ย

static void Main(string[] args)
{
using (var db = new ProductContext())
{
// Add a food category
var food = new Category { CategoryId = “FOOD”, Name = “Foods” };
db.Categories.Add(food);
int recordsAffected = db.SaveChanges();

Console.WriteLine(
“Saved {0} entities to the database, press any key to exit.”, recordsAffected);

Console.ReadKey();
}
}

หลังจาก run console ดังกล่าวแล้ว DbContext จะสร้างฐานข้อมูล SimpleCodeFirstDemo.ProductContext ไว้ที่ localhost\SQLEXPRESS(หรือ .\SQLEXPRESS) ซึ่งเป็นฐานข้อมูล SQL Server Express ที่ติดมาพร้อมกับ VS Studio ให้เราอัตโนมัติอยู่แล้ว ตามภาพข้างล่างเป็นการ Connection ไปที่ฐานข้อมูล localhost\SQLEXPRESS

เมื่อลอง connect เข้าไปเราจะพบ Table Products และ Catagories สร้างไว้เรียบร้อย พร้อมกับมี ข้อมูล Food 1 record ที่เราได้เขียน code คำสั่งแรกไปแล้วข้างต้น

เมื่อกลับไปดูที่ class ProductContext พบว่า table ที่สร้างขึ้น ถูกตั้งชื่อและสร้าง table schema ตามชื่อ properties Categories กับ Products กับ สร้าง schema จาก properties ตามชนิดของ generic DbSet ของ Categories กับ Products และนี่ก็คือส่วนหนึ่งของ “convention over configuration” นั่นเอง

มาเล่นกับตัวอย่างที่ทำการอ่านและ เขียนข้อมูลดูบ้าง …

1. ใส่ code ตามข้างล่างนี้ลงไปที่ main program

static void Main(string[] args)
{
using (var db = new ProductContext())
{
// Use Find to locate the Food category
var food = db.Categories.Find(“FOOD”);
if (food == null)
{
food = new Category { CategoryId = “FOOD”, Name = “Foods” };
db.Categories.Add(food);
}

// Create a new Food product
Console.Write(“Please enter a name for a new food: “);
var productName = Console.ReadLine();

var product = new Product { Name = productName, Category = food };
db.Products.Add(product);

int recordsAffected = db.SaveChanges();

Console.WriteLine(
“Saved {0} entities to the database.”,
recordsAffected);

// Query for all Food products using LINQ
var allFoods = from p in db.Products
where p.CategoryId == “FOOD”
orderby p.Name
select p;

Console.WriteLine(“All foods in database:”);
foreach (var item in allFoods)
{
Console.WriteLine(” – {0}”, item.Name);
}

Console.WriteLine(“Press any key to exit.”);
Console.ReadKey();
}
}

2. ลอง run program ทดลองเล่นดู จะมีหน้าตาออกมาคล้ายๆแบบข้างล่างนี้ครับ

มีคำสั่งที่จำเป็นอีกคำสั่งที่น่าใช้นั่นก็คือ

Database.SetInitializer<ProductContext>(new DropCreateDatabaseIfModelChanges<ProductContext>());

คำสั่งบรรทัดนี้จะทำการ drop และ create database ใหม่หมดที่อยู่ภายใต้ context ProductContext ในกรณีที่ มีการเปลี่ยน Schema ที่ตัว domain model ของเราครับ ให้คุณลอง เพิ่ม property เข้าไปที่ model สัก field หนึ่ง ทุก table ที่มีอยู่จะถูก drop หมดและถูกสร้างขั้นมาใหม่ให้โดยอัตโนมัติ ซึ่งมีประโยชน์มากตอนพัฒนา พอ model มีการเปลี่ยนแปลงเราไม่ต้องมากังวลเรื่อง table schema ในฐานข้อมูล เรื่องการ mapping model กับ database หรือ persitance นี้อีกต่อไป

ในกรณีที่เราต้องการเพิ่ม model เข้าไปทุกๆครั้งที่สร้าง table ขึ้นมาใหม่ เช่น ตัวอย่างของเราต้องการเพิ่ม Category FOOD เข้าไปทุกๆครั้ง เพราะต้องการทดสอบ method ค้นหา หรือต้องการ mockup เรื่องราวที่มี models ใดๆเกิดขึ้นมาก่อน ก็ทำตามนี้ครับ

1. สร้าง class ProductContextInitializer ที่ base บน DropCreateDatabaseIfModelChanges แล้ว override method Seed แบบ code ข้างล่าง

public class ProductContextInitializer : DropCreateDatabaseIfModelChanges<ProductContext>
{

        protected override void Seed(ProductContext context)
        {
            var food = new Category { CategoryId = “FOOD”, Name = “Foods” };
            context.Categories.Add(food);
            context.SaveChanges();
        }

}

2. เปลี่ยนตอนเรียก method Database.SetInitializer โดยให้ใส่ initializer นี้เข้าไป

Database.SetInitializer<ProductContext>(new ProductContextInitializer());

เรียบร้อยครับ เมื่อไรก็ตามที่มีการ drop ฐานข้อมูลแล้วสร้างใหม่ ก็จะมีข้อมูล FOOD เข้าไปใน table Categories ให้เราด้วยทุกครั้ง

Data Annotations

จาก model คุณจะเห็นว่าด้วยวิธีการของ Code-First นั้นมันมีชื่อ default conventions อยู่ เพื่อที่จะไว้สร้าง table จาก model ของเราใน database แต่ในกรณีที่ domain model ของเราไม่เป็นไปตาม conventions นั้นจะทำอย่างไร เช่น

เพิ่ม domain model Supplier เข้าไปใน projects

public class Supplier
{
public string SupplierCode { get; set; }
public string Name { get; set; }
}

เพิ่ม property Suppliers เป็น DbSet ของ Supplier เข้าไปใน ProductContext

public class ProductContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
}

คุณจะเห็นว่า domain model Supplier มี key เป็น SupplierCode แต่ default conventions ต้องลงท้ายชื่อ field ด้วย Id จึงจะทำให้ EF รู้ว่านี่คือ Key ที่จะนำไปสร้าง table schema ที่ฐานข้อมูล(./SQLEXPRESS) ทดลอง run projects ดูก็จะขึ้น error แบบนี้ครับ

One or more validation errors were detected during model generation:

System.Data.Edm.EdmEntityType: : EntityType ‘Supplier’ has no key defined. Define the key for this EntityType.
System.Data.Edm.EdmEntitySet: EntityType: EntitySet �Suppliers� is based on type �Supplier� that has no keys defined.

แก้ไขโดยใช้ Data Annotations

1.add reference System.ComponentModel.DataAnnotations เข้ามาใน project แล้ว using ใน domain model Supplier

2 เติม Annotations [Key] เข้าไปที่ field SupplierCode  ของ domain model Supplier แบบนี้

public class Supplier
{
[Key]
public string SupplierCode { get; set; }
public string Name { get; set; }
}

3. ลอง run main programe ใหม่อีครั้งครับ ไม่เกิด error แล้วและ คุณจะได้ table Supplier อีก 1 table ครับ

Fluent API

ในส่วนก่อนหน้านี้ เราได้รู้ว่าการใช้ Data Annotations เพื่อช่วยเสริมหรือแทนที่สิ่งที่ถูกตรวจพบโดย conventions วิธีอื่น ๆ เพื่อไปกำหนดค่า model โดยผ่านทาง Code First fluent API fluent API จัดว่าเป็นคุณลักษณะขั้นสูงผมจะแนะนำให้ใช้ Data Annotations ก็เพียงพอแล้ว เว้นแต่ความต้องการของคุณซับซ้อนกว่า Data Annotations ที่มี คุณก็ใช้ fluent API ได้

ในการใช้ fluent API ให้ override OnModelCreating method ใน DbContext สมมติเราต้องการให้เจาะลงให้ Supplier Name มีค่าว่างไม่ได้ เพิ่ม code จะมีหน้าตาแบบนี้

public class ProductContext : DbContext
{
    public ProductContext() { }
    public DbSet<Category> Categories { get; set; }
    public DbSet<Product> Products { get; set; }
    public DbSet<Supplier> Suppliers { get; set; }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Supplier>().Property(s => s.Name).IsRequired();
    }
}

พอคุณ run code นี้อีกครั้ง DbContext จะสร้าง table Supplier ที่ required field Name ให้คุณโดยอัตโนมัติ

Deploy Application

เมื่อเราต้องการ deploy application ของเรา และต้องเปลี่ยนฐานข้อมูล ซึ่งโดย default ที่ Code-First จะ config ฐานข้อมูลไปที่ localhost\SQLEXPRESS ซึ่งเราต้องการให้เปลี่ยนไปเครื่องอื่น ทำได้ตามนี้ครับ

1. เพิ่ม config connection string ไว้ที่ App.config หรือ Web.config

<connectionStrings>
  <add
    name="ProductContext"
    providerName="System.Data.SqlClient"
    connectionString="Server=MyServer;Database=SimpleCodeFirstDemo.ProductContext;
    Integrated Security=True;MultipleActiveResultSets=True;" />
</connectionStrings>

 2. กลับไป update ProductContext โดยเพิ่ม initial base contructor ใส่ argrument connection string “name=ProductContext” ที่ได้เพิ่มเข้าไปข้างต้น ตาม code ข้างล่างนี้

public ProductContext() : base(“name=ProductContext”) { }

เสร็จแล้วครับ ง่ายมากเลยใช่มั้ยละ Code-First กับ EF 4.1

ขอบคุณครับ …

แหล่งข้อมูลจาก

Advertisements

#entity-framework