ต่อจากตอนที่แล้ว
สำหรับ Relation หรือความสัมพันธ์ที่ใช้ใน ActiveRecord จะมีหลักๆดังนี้
- BelongsTo - ใช้ระบุความสัมพันธ์แบบ many-to-one หรือ one-to-one
- HasMany - ใช้ระบุความสัมพันธ์แบบ one-to-many
- HasAndBelongsToMany - ใช้ระบุความสัมพันธ์แบบ many-to-many
สมมติเราเพิ่มข้อมูลลงไปอีกตาราง คือ files เพื่อเอาไว้เก็บว่าแต่ละคนมีไฟล์อะไรบ้าง โดยมี Schema เป็นแบบนี้
เมื่อเอามาแปลงเป็นคลาสก็จะได้หน้าตาเป็น
[ActiveRecord("files")]
public class UserFile : ActiveRecordBase<UserFile>
{
[PrimaryKey(PrimaryKeyType.Native)]
public virtual int ID { get; set; }
[BelongsTo("user_id")]
public virtual User Owner { get; set; }
[Property("Filename")]
public virtual string Filename { get; set; }
[Property("type")]
public virtual string ContentType { get; set; }
[Property("data")]
public virtual byte[] FileData { get; set; }
[Property("size")]
public virtual int FileSize { get; set; }
}
คราวนี้เปลี่ยนมา inherit จาก ActiveRecordBase
สำหรับ Property Owner จะเป็นการแมป Property นี้เข้ากับคลาส User โดยใช้ฟิลด์ user_id เพื่อบอกว่าจะแมปเข้ากับ object ไหนให้ดูจาก user_id เป็นหลัก
เซฟคลาสนี้ในไฟล์ชื่อ App_code/UserFile.cs
จากนั้นเราจะต้องไม่ลืมเพิ่มข้อมูลของคลาส UserFile ลงใน GlobalApplication.cs เพื่อให้ ActiveRecord รู้ว่าเราจะโหลดคลาสนี้เข้าไปด้วย
public static void InitActiveRecord(IConfigurationSource source)
{
ActiveRecordStarter.Initialize(source,
typeof(User),
typeof(UserFile)
)
;
}
หลังจากนั้น เราก็จะต้องมาแก้ไขคลาส User เพื่อเพิ่ม Property Files เพื่อบอกว่ามีไฟล์อะไรเก็บไว้บ้าง
[ActiveRecord]
public class User : ActiveRecordValidationBase
{
[PrimaryKey(PrimaryKeyType.Native)]
public virtual int ID { get; set; }
[Property]
public virtual string Email { get; set; }
[Property]
public virtual string Password { get; set; }
[HasMany(typeof(UserFile), "user_id", "files")]
public virtual IList<UserFile> Files { get; set; }
}
ก่อนอื่นก็จะต้องสร้างหน้าฟอร์มสำหรับการอัพโหลดไฟล์ก่อน ตั้งชื่อว่า UploadForm.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UploadForm.aspx.cs" Inherits="UploadForm" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:DropDownList ID="DropDownList1" runat="server"
DataSourceID="ObjectDataSource1" DataTextField="Email" DataValueField="ID">
</asp:DropDownList>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="FindAll" TypeName="User"></asp:ObjectDataSource>
</div>
<asp:FileUpload ID="FileUpload1" runat="server" />
<br />
<asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Button" />
<asp:Label ID="Label1" runat="server"></asp:Label>
</form>
</body>
</html>
โดยมีเนื้อหาของ Code Behind เป็น
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Xml.Linq;
public partial class UploadForm : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{ }
protected void Button1_Click(object sender, EventArgs e)
{
UserFile uf = new UserFile();
uf.Filename = FileUpload1.FileName;
uf.FileSize = FileUpload1.FileBytes.Length;
uf.ContentType = FileUpload1.PostedFile.ContentType;
uf.FileData = FileUpload1.FileBytes;
uf.Owner = global::User.Find(Convert.ToInt32(DropDownList1.SelectedValue));
uf.Save();
Label1.Text = "Upload Completed";
}
}
จากนั้นลองอัพโหลดไฟล์เข้าไปซัก 2 - 3 ไฟล์
ทีนี้เราลองกลับมาแก้ไขหน้า default ให้แสดงผลรายการของไฟล์ด้วย
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="ObjectDataSource1">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" SortExpression="ID" />
<asp:BoundField DataField="Email" HeaderText="Email" SortExpression="Email" />
<asp:BoundField DataField="Password" HeaderText="Password" SortExpression="Password" />
<asp:TemplateField HeaderText="File">
<ItemTemplate>
<asp:Repeater ID="Repeater1" runat="server" DataSource='<%# Eval("Files") %>'>
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%#Eval("Filename") %>
(<%#Eval("FileSize") %>)</li></ItemTemplate>
<FooterTemplate>
</ul></FooterTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="FindAll"
TypeName="User"></asp:ObjectDataSource>
</form>
</body>
</html>
ที่ทำคือเพิ่ม template field เข้าไปหนึ่งคอลัมน์ใน GridView จากนั้นก็ Bind ตัว property Files เข้าไปกับ Repeater แล้วให้ Repeater แสดงชื่อไฟล์พร้อมกับขนาดออกมา
ทีนี้สมมติว่าเราใส่ tag ให้กับไฟล์ด้วยล่ะ จะทำยังไง
สมมติให้มีอีกตารางชื่อว่า tags
และตารางที่ทำหน้าที่เชื่อมระหว่าง tags และ files ให้ชื่อว่า file_tag
ทีนี้สำหรับคลาส Tag ก็ทำคล้ายๆคลาส UserFile ก็คือดังข้างล่าง
using System;
using Castle.ActiveRecord;
using System.Collections.Generic;
[ActiveRecord("Tags")]
public class Tag : ActiveRecordBase<Tag>
{
[PrimaryKey(PrimaryKeyType.Native)]
public virtual int ID { get; set; }
[Property("tag")]
public virtual string TagName { get; set; }
[HasAndBelongsToMany(typeof(UserFile), Table="file_tag", ColumnKey="tag_id", ColumnRef="file_id", Inverse=true )]
public virtual IList<UserFile> Files { get; set; }
public static Tag FindByTag(string tag)
{
Tag t = Tag.FindFirst(Expression.Like("TagName", tag));
return t;
}
}
ตรงนี้เพิ่มเมธอด FindByTag เข้ามาโดยจะดูว่ามี Tag ดังกล่าวอยู่ในระบบอยู่แล้วหรือไม่
ทีนี้ลองกลับไปแก้ คลาส UserFile โดยใส่ Property Tags เข้าไปดูบ้าง
[ActiveRecord("files")]
public class UserFile : ActiveRecordBase<UserFile>
{
[PrimaryKey(PrimaryKeyType.Native)]
public virtual int ID { get; set; }
[BelongsTo("user_id")]
public virtual User Owner { get; set; }
[Property("Filename")]
public virtual string Filename { get; set; }
[Property("type")]
public virtual string ContentType { get; set; }
[Property("data")]
public virtual byte[] FileData { get; set; }
[Property("size")]
public virtual int FileSize { get; set; }
[HasAndBelongsToMany(typeof(Tag), RelationType.Bag, Table="file_tag", ColumnKey="file_id", ColumnRef="tag_id")]
public virtual IList<Tag> Tags { get; set; }
}
จัดการแก้หน้า UploadForm ให้รองรับเรื่อง Tag ด้วยการเพิ่ม textbox เข้าไปหนึ่งอัน
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UploadForm.aspx.cs" Inherits="UploadForm" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:DropDownList ID="DropDownList1" runat="server" DataSourceID="ObjectDataSource1"
DataTextField="Email" DataValueField="ID">
</asp:DropDownList>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="FindAll"
TypeName="User"></asp:ObjectDataSource>
</div>
<asp:FileUpload ID="FileUpload1" runat="server" />
<br />
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<br />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" />
<asp:Label ID="Label1" runat="server"></asp:Label>
</form>
</body>
</html>
จากนั้นก็ไปแกตอนกดปุ่ม
protected void Button1_Click(object sender, EventArgs e)
{
UserFile uf = new UserFile();
uf.Filename = FileUpload1.FileName;
uf.FileSize = FileUpload1.FileBytes.Length;
uf.ContentType = FileUpload1.PostedFile.ContentType;
uf.FileData = FileUpload1.FileBytes;
uf.Tags = new List<Tag>();
foreach (string tag in TextBox1.Text.Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries ))
{
string trimmedTag = tag.Trim();
if (!string.IsNullOrEmpty(trimmedTag))
{
Tag t = Tag.FindByTag(trimmedTag);
if (t == null)
{
t = new Tag();
t.TagName = trimmedTag;
t.Save();
}
uf.Tags.Add(t);
}
}
uf.Owner = global::User.Find(Convert.ToInt32(DropDownList1.SelectedValue));
uf.Save();
Label1.Text = "Upload Completed";
}
จุดนี้ที่เพิ่มเข้ามาก็คือจะดูว่าใน tag ที่เพิ่มเข้ามานั้น มีอยู่แล้วรึเปล่า ถ้าไม่มีก็จะเพิ่มเข้าไปในฐานข้อมูล แต่ถ้ามีก็จะใช้ tag นั้นแปะเข้าไปใน UserFile ของเราเลย ทีนี้พอลองรันดูก็จะเห็นว่าข้อมูลเข้าไปตามที่เรากำหนด
วิธีทดสอบง่ายๆก็เลยลองสร้างหน้าที่ใช้แสดงรายการไฟล์ต่างๆของ tag ขึ้นมาดู
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True" DataSourceID="ObjectDataSource1"
DataTextField="TagName" DataValueField="ID" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged">
</asp:DropDownList>
<asp:GridView ID="GridView1" runat="server">
</asp:GridView>
</div>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="FindAll"
TypeName="Tag"></asp:ObjectDataSource>
</form>
</body>
</html>
โดยมี Code Behind เป็น
using System;
public partial class Default2 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{ }
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
GridView1.DataSource = Tag.Find(Convert.ToInt32(DropDownList1.SelectedValue)).Files;
GridView1.DataBind();
}
}
เมื่อทดลองรันดู ก็ได้ผลน่าพอใจสำหรับการใช้งาน Active Record แล้วนะ
สำหรับตอนนี้ก็ขอจบลงเพียงเท่านี้ ในตอนหน้ากำลังคิดอยู่ว่าจะเป็นเรื่อง transaction, validation หรือจะเป็นเรื่อง lazy loading ดี
ไว้เจอกันเมื่อหาเวลาได้







น่าสนใจมาก เลยคะ ตกลงแล้ว ORM
น่าสนใจมาก เลยคะ
ตกลงแล้ว ORM ตัวไหน น่าใช้ที่สุดคะ สำหรับ .NET
Post new comment