Realtime™ Development Framework applied to LINQ (.NET Language-Integrated Query) LINQ เปลยนชอมาจาก DLINQ และเปน ORM (Object-
Relational Mapping) ทจะ Implement ลงใน Visual Studio 2008 เปนเทคนคในการเขยนโปรแกรมเพอท าการแปลงโครงสราง
(mapping) จาก Relational Database ใหมาอยในรปของ OO โดย Type ทางฝง Database และทางฝงของ OO มความเขากน
ได nw 12/13/2007
Realtime™ Development Framework applied to LINQ (.NET Language-Integrated Query)
LINQ เปลยนชอมาจาก DLINQ และเปน ORM (Object-Relational Mapping) ทจะ Implement ลงใน
Visual Studio 2008 เปนเทคนคในการเขยนโปรแกรมเพอท าการแปลง โครงสราง (mapping) จาก Relational
Database ใหมาอยในรปของ OO โดย Type ทางฝง Database และทางฝงของ OO มความเขากนได
โครงสรางของ LINQ ประกอบดวยสวนตางๆ ดงน
LINQ to Objects เพอใช LINQ ตดตอกบ Object อนๆ
LINQ to Datasets, LINQ to SQL, LINQ to Entities ใชในการตดตอกบ Relational Database
LINQ to XML ใชในการตดตอกบ XML Document โดยเฉพาะ
ใน Realtime™ Development Framework จะอมพลเมนตเฉพาะ LINQ to SQL เทานน โดยสราง Class
ออกมาแทน Object ตางๆ ใน Database เชน Table, View และสราง Function, Method ตางๆ เพอใชในการ
Select, Insert, Delete, Update ขอมลตางๆใน Database เรยกวา Virtual Object Database ซงคอมไพลและ
แยกเกบไวเปน Library ในรป DLL File การน า LINQ to SQL มาใชงานจะชวยแกปญหาตางๆ กลาวคอ เดมท
SQL Statement ทอยในรปตวแปร string เพอทน าไป Execute ใน database แลวรบคาท Return กลบมาใสใน
Result Set ตางๆ เชน DataTable หรอ DataReader ซงชดค าสง SQL ดงกลาวยงยากตอการตรวจสอบความ
ผดพลาด และไมม IntelliSense มาชวยในการเขย น เราจะรวาการเขยนค าสงนนผดพลาดกตอเมอมนไดถก
Execute ลงใน Database แลว และมการ Throw Database Exception นนกลบขนมา จากปญหาเหลานตว
LINQ จะชวยใหเราสามารถตรวจสอบขอผดพลาดของการเขยน Query ในระดบ Compile time นอกจากนนยงม
Intellisence มาชวยใหพมพผดนอยลงอกดวย
Database
• NVarChar
Mapper
• String
Mapping Table ของ LINQ to SQL
จะมลกษณะดงภาพดานลางโดยทฝงซายจะแทนโครงสรางของ Database และทางฝงขวาคอ OO
สวนทเพมเตมขนมา จาก ORM เวอรชนเดม คอ DataContext ซงเสมอนเปนทอสงหลกทใชในการดง
ขอมลจาก Database และท าการสงขอมลทมการ เปลยนแปลงกลบลงไปยง Database เราสามารถใช DataContext ไดในลกษณะเดยวกน กบ ADO.NET Connection ทวไป (เชน SqlConnection ซงสามารถระบ
Connection String ไดโดยตรงหรอผาน app.config) หนาทหลกของ DataContext คอ
1. สราง Database Connection 2. แปลง LINQ Query Syntax ใหเปน SQL Statement เพอเตรยม Execute
3. แปลง Object ทถก Query ขนมา ใหกลบลงไปยง Database ในรปแบบท Database Modeling
(.dbml) ไดท าการ Mapping เขามา เชน Table, Stored Procedure เปนตน.
วงจรการท างานของ LINQ To SQL
1. สราง LINQ Query Syntax ส าหรบกระท ากบฐานขอมลตามตองการ
2. LINQ Query Syntax จะถกแปลงเปน SQL Statement 3. SQL Statement จะถก Execute ในลกษณะการ Execute Database ทวไป โดยสงขอมลกลบมาใน
รปแบบ Datatype ของ Database เชน Row 4. ขอมลทคนกลบมาจะถกแปลงใหอยในรป ObjectType ตามท Mapping Table ระบ
5. กรณเปนการปรบเปลยน Property หรอเพม Entity Object ตางๆ จะเรมตนโดยการเรยก Method SubmitChanges() ใน Class ชอ DataContext
6. แปลงกลม Entity หรอ Property ทระบจะถกแปลงเปน SQL Statement ในรปของ DML หรอ Stored
Procedure กลบไปยง SQL Server แลวประมวลผลในลกษณะเดยวกน
Implicitly typed local variables
คอการใช keyword var เพอประกาศตวแปร หรอสมาชกแบบอนๆ โดยไมจ าเปนตองก าหนด datatype
ทชดเจน ตวแปรทถกสรางขนอาจรองรบ datatype ประเภทไหนกได
การใช implicitly type local variable มลกษณะคลายกบ Object Type แตตางกนตรงท var เปน
Primitive Type (ไมตองม pointer ส าหรบชต าแหนงใน memory) ท าใหการเรยกใช ไมตองมการ cast Pointer Type กลบไปมา สวนขอเสยของการใช variant (var) คอใชหนวยความจ ามากและไมเปน strong typed ซงเปน
สงส าคญใน .Net ตวอยางการประกาศก าหนดไดดงน
var foo = 0.8;
การก าหนดคาครงแรกของตวแปร จะเปนบรรทดทเรมระบให Compiler รวาเปนตวแปรประเภทใดแน นอกจากนเรายงสามารถใช variant รวมกบ object initialization ไดดวย
Anonymous types
เปนคณสมบตเรมมใชใน C#3.0 ชวยอ านวยความสะดวกในการสราง และก าหนดคาเรมตน ใหแก object
โดยไมตองก าหนด type ท าใหการเขยนโคด ยดหยนขนเราสมารถสรางและก าหนดคาเรมตนให object ทสราง
จากคลาสได โดยอาศย object initialization ไดดงน
var mySudent = new Student{ name = "Pravit", Age = 45, address = "109/20"
};
class Student
{
public string name;
public string address;
int age;
public int Age
{
get { return age; }
set { age = value; }
}
}
จากโคดขางตนเราสงเกตไดวาเราก าหนดให object myStudent ม type เปน var ซงเปน implicit type local
variables เมอ compile แลว myStudent จะไดรบ type ทแทจรงคอ Student เราสามารถเขยนโคดไดอก
ลกษณะเพอความยดหยนของ Anonymous types เราสามารถก าหนดคาเรมตนใหกบ object ไดดงน
var mySudent = new { name = "Pravit", Age = 45, address = "109/29" };
โปรดสงเกตวาเราไดละชอ constructor ออกไปซงมความหมายวาไมไดระบ type ทใชในการก าหนดคาเรมตน
ดงนนเราจงไมรวา object myStudent เปน type อะไรแนแตโปรแกรมกท างานโดยไม error เพราะ compiler จะ
สราง type ใหเราขณะคอมไพล
ขอสรป Anonymous types
ชวยในการสราง object โดยไมตองระบ type
Syntax คลาย object initializes แตไมระบ type
มกใชกบ Implicitly typed local avariables
Complier จะสราง type ใหขณะ complie (late binding)
Implicitly typed arrays
เปนคณสมบตเรมใชใน C#3.0 ชวยอ านวยความสะดวกในการประกาศ และก าหนดคาเรมตนใหแก array
โดยไมตองก าหนด type, size and dimension ท าใหการเขยนโคดยดหยนขน
เดมทใน C# 2.0 เราตองประกาศและก าหนดคาเรมตนใหแก array โดยค าสงตอไปน
int[] arr = new int[] { 1, 2, 3, 4 };
แตใน C#3.0 เราสามารถเขยนไดโดยค าสงตอไปน
var arr = new[] { 1, 2, 3, 4 };
โปรดสงเกตวาเราไมไดก าหนด type เมอ compile arr จะม data type เปน int
ขอสรป Implicitly typed arrays
ชวยใหสราง array โดยไมตองระบ type
ชวยใหสราง syntax คลายการนยาม array ธรรมดาแตไมระบ type
มกจะใชรวมกบ implicitly type local variables
Compiler จะก าหนด type ใหขณะ compile
Lambda Expression
เปนคณลกษณะทเพมขนใน C# 3.0 ท าให anonymous method ของ C# 2.0 กลายเปนสงทลาสมย
Lambda Expression มประโยชนในการเขยนโคดทเกยวของกบ delegate, DLINQ และ expression tree และ
ชวยในการเขยนโคดใหกระชบตามแบบในภาษาตระกล c และสงางามตามแบบ C# พจารณาจากโคดตอไปน
x => x > 10
นคอรปแบบในรปสามญทสด มนคอนพจน Boolean ทมคาเปนจรงหาก x มคามากกวา 10 เราสามารถใชภาษา
LINQ ไดหากใช DLINQ (DLINQ คอภาษาใหมโดยใช LINQ ไดถกผนวกเปนหนงใน C# 3.0) เมอ compile แลว
มกจะพบ Lambda Expression ปรากฏอยดวย โปรดพจารณาตวอยาง DLINQ ดงตอไปน
var myQ =
from myTable in orders, c in customers
where ( myTable.ShipCity == "London") &&
( myTable.CustomerID == c.CustomerID)
select new
{
myTable.OrderDate,
c.CompanyName,
c.ContactTitle,
c.ContactName
};
โคดขางบนคอการสบคนขอมลดวยภาษา DLINQ ในโคด นจะเหนวามตารางสองตาราง อนแรกเปน object ชอ
myTable ซงอยภายใน object collection ชอ order อกตารางชอ c อยภายในฐานขอมลชอ customers เราใช ภาษา DLINQ เพอสบคนขอมลพรอมๆ กนทงสองตารางโคดนเมอคอมไพลแลวจะเปนดงน
var myQ = orders
.Where(myTable => myTable.ShipCity == "London")
.SelectMany(myTable => customers
.Where(c => myTable.CustomerID == c.CustomerID)
.Select(c => new
{
myTable.OrderDate,
c.CompanyName,
c.ContactTitle,
c.ContactName
}));
ในโคดนมสวนทเปน Lambda Expression อย 2 แหงคอ
myTable => myTable.ShipCity == "London"
และ c => myTable.CustomerID == c.CustomerID
ในสองบรรทดนเราจะเหนวา Lambda Expression อยในรปแบบ Parameter => Expression โดยบรรทดแรก
Parameter คอ myTable สวน expression คอ myTable.ShipCity == "London" C# 3.0 ไดนยาม generic delegate ไวใหแลวสวนหนงซงมรปแบบดงน
delegate T Func<A, T>(A param)
delegate T Func<A0, A1, T>(A0 param0, A1 param1)
delegate T Func<A0, A1, A2, T>(A0 param0, A1 param1, A2 param2)
delegate T Func<A0, A1, A2, A3, T>(A0 param0, A1 param1, A2 param2, A3
param3)
ท าใหเราอาจนยาม Lambda Expression ใดๆ โดยไมตองนยาม delegate กอนเพยงใชงานตามรปแบบทนยามไว แลวดงแสดงขางบน ยกตวอยางเชน Lampda Expression เพอตรวจสอบเลขคมดงน
Func<int, bool> isEven = i => (i&1) == 0
ขอสรปเรอง Lambda Expression คอคณลกษณะทเพมขนใน C#3.0 เพอใชแทน anonymous method ใน C#2.0
ท าใหนยาม delegate ไดกระชบขน
ใชรวมกบภาษา LINQ
Query Expression
Query syntax ตองเรมตนดวย from และ ลงทายดวย select or group ระหวาง from และ select or
group สามารถบรรจส งใดสงหนงดงน where, orderby, join, let สามารถใช keyword into ส าหรบผลการ join
หรอ group ได
ตวอยางการใช Query expression เบองตน โดยใชกบ List ซงยงไมไดตอกบฐานขอมล
ก าหนดให var scores = new[] { 90, 71, 82, 93, 75, 82 }; List<City> cities = new List<City>();
List<Country> countries = new List<Country>();
public class Country
{
public string Name { get; set; }
public int Area { get; set; }
public List<City> City { get; set; }
}
public class City
{
public int Population { get; set; }
}
Query variable
วธการหาคะแนนมากกวา 80 และเรยงล าดบ จากมากไปหานอย
IEnumerable<int> scoreQuery =
from score in scores
where score > 80
orderby score descending
select score;
output = 93 90 82 82
วธการหาคะแนนสงสด
int highestScore =
(from score in scores
select score)
.Max();
หรอท าการแบงกลมควรออกกอนดงน
IEnumerable<int> scoreQuery =
from score in scores
select score;
int highScore = scoreQuery.Max();
output = 93
วธการหาCity Population > 1000
//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 1000
select city;
หรอ
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population >
1000);
วธการหา City ใน country ท Population > 1000 IEnumerable<City> largeCityList =
from country in countries
from city in country.City
where city.Population > 1000
select city;
Syntax ตางๆ ใน query expression
Explicitly and Implicit Typing of Query Variables
// Use of var is optional here and in all queries.
// queryCities is an IEnumerable<City> just as
// when it is explicitly typed.
var queryCities =
from city in cities
where city.Population > 100
select city;
Ending a Query Expression
Select with group by
var queryCountryGroups =
from country in countries
group country by country.Name[0];
Select with order by
IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;
var queryNameAndPop =
from country in countries
select new { Name = country.Name, Pop = country.Population
};
Continuations with "into" var percentileQuery =
from country in countries
let percentile = (int)country.Area / 2
group country by percentile into countryGroup
where countryGroup.Key >= 20
orderby countryGroup.Key
select countryGroup;
where Clause
IEnumerable<City> queryCityPop =
from city in cities
where city.Population < 200000 && city.Population > 100000
select city;
order by Clause
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area > 500000, country.Area descending
select country;
Join Clause
var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new { Category = cat, Name = prod.Name };
Let Clause
IEnumerable<int> queryScr =
from scr in scores
let scMin = scr-10
select scMin;
output = 80, 61, 72, 83, 65, 72
Subqueries in a Query Expression
var queryGroupMax =
from student in students
group student by student.GradeLevel into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore =
(from student2 in studentGroup
select student2.Scores.Average())
.Max()
};
ตวอยางการ Join ทประกอบดวย inner join, group join, left outer join
class JoinDemonstration
{
#region Data
class Product
{
public string Name { get; set; }
public string CategoryID { get; set; }
}
class Category
{
public string Name { get; set; }
public string ID { get; set; }
}
// Specify the first data source.
List<Category> categories = new List<Category>()
{
new Category(){Name="Beverages", ID="001"},
new Category(){ Name="Condiments", ID="002"},
new Category(){ Name="Vegetables", ID="003"},
new Category() { Name="Grains", ID="004"},
new Category() { Name="Fruit", ID="005"}
};
// Specify the second data source.
List<Product> products = new List<Product>()
{
new Product{Name="Cola", CategoryID="001"},
new Product{Name="Tea", CategoryID="001"},
new Product{Name="Mustard", CategoryID="002"},
new Product{Name="Pickles", CategoryID="002"},
new Product{Name="Carrots", CategoryID="003"},
new Product{Name="Bok Choy", CategoryID="003"},
new Product{Name="Peaches", CategoryID="005"},
new Product{Name="Melons", CategoryID="005"},
new Product{Name="CityCar", CategoryID="008"},
new Product{Name="Toyota", CategoryID="008"},
};
#endregion
static void Main(string[] args)
{
JoinDemonstration app = new JoinDemonstration();
app.InnerJoin();
app.GroupJoin();
app.GroupInnerJoin();
app.GroupJoin3();
app.LeftOuterJoin();
app.LeftOuterJoin2();
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
void InnerJoin()
{
// Create the query that selects
// a property from each element.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { CategoryID = category.ID,CategoryName =
category.Name, Product = prod.Name };
Console.WriteLine("InnerJoin:");
// Execute the query. Access results
// with a simple foreach statement.
foreach (var item in innerJoinQuery)
{
Console.WriteLine("{0} {1} {2}",
item.Product,item.CategoryName, item.CategoryID);
}
Console.WriteLine("InnerJoin: {0} items in 1 group.",
innerJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin()
{
// This is a demonstration query to show the output
// of a "raw" group join. A more typical group join
// is shown in the GroupInnerJoin method.
var groupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
into prodGroup
select prodGroup;
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Simple GroupJoin:");
// A nested foreach statement is required to access group
items.
foreach (var prodGrouping in groupJoinQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name,
item.CategoryID);
}
}
Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed
groups", totalItems, groupJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupInnerJoin()
{
var groupJoinQuery2 =
from category in categories
orderby category.ID
join prod in products on category.ID equals prod.CategoryID
into prodGroup
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupInnerJoin:");
foreach (var productGroup in groupJoinQuery2)
{
Console.WriteLine(productGroup.Category);
foreach (var prodItem in productGroup.Products)
{
totalItems++;
Console.WriteLine(" {0,-10} {1}", prodItem.Name,
prodItem.CategoryID);
}
}
Console.WriteLine("GroupInnerJoin: {0} items in {1} named
groups", totalItems, groupJoinQuery2.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin3()
{
var groupJoinQuery3 =
from category in categories
join product in products on category.ID equals
product.CategoryID into prodGroup
from prod in prodGroup
orderby prod.CategoryID
select new { Category = prod.CategoryID, ProductName =
prod.Name };
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupJoin3:");
foreach (var item in groupJoinQuery3)
{
totalItems++;
Console.WriteLine(" {0}:{1}", item.ProductName,
item.Category);
}
Console.WriteLine("GroupJoin3: {0} items in 1 group",
totalItems, groupJoinQuery3.Count());
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin()
{
// Create the query.
var leftOuterQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
into prodGroup
select prodGroup.DefaultIfEmpty(new Product() { Name =
"Non", CategoryID = category.ID });
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Left Outer Join:");
// A nested foreach statement is required to access group
items
foreach (var prodGrouping in leftOuterQuery)
{
Console.WriteLine("Group:", prodGrouping.Count());
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name,
item.CategoryID);
}
}
Console.WriteLine("LeftOuterJoin: {0} items in {1} groups",
totalItems, leftOuterQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin2()
{
// Create the query.
var leftOuterQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID
into prodGroup
from item in prodGroup.DefaultIfEmpty()
select new { Name = item == null ? "Nothing!" : item.Name,
CategoryID = category.ID };
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group",
leftOuterQuery2.Count());
// Store the count of total items
int totalItems = 0;
Console.WriteLine("Left Outer Join 2:");
// Groups have been flattened.
foreach (var item in leftOuterQuery2)
{
totalItems++;
Console.WriteLine("{0,-10}{1}", item.Name,
item.CategoryID);
}
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group",
totalItems);
}
}
MapperDataContext : DataContext
MapperDataContext เปนคลาสจดการฐานขอมลท Linq Mapper เรยกใชงาน โดยจะถกสรางใหแตละ Identity ทเขาสระบบผาน Realtime™ Development Framework : eFx ซงสามารถเรยกใชงานไดดงน
MapperDataContext mx;
mx = RuntimeMonitor.GetObservor().GetCurrentDataContext();
ค านยาม Realtime™ Development Framework : eFx
Realtime™ Development Environment : RTDE
ตวอยางการใชงาน
การเพมขอมล EcUser ecUsr = new EcUser();
ecUsr.ClientNo = "0001";
ecUsr.ClientNoOwner = "0001";
ecUsr.ClientTypeNo = "01";
ecUsr.UserName = "[email protected]";
ecUsr.UserPassword = "l07WGW0fkXGZ4E14sGyfvw";
ecUsr.UserStatus = "1";
ecUsr.UserCreateDate = DateTime.Now;
ecUsr.ContactNo = "101049";
try
{
mx.GetTable<EcUser>().InsertOnSubmit(ecUsr);
mx.SubmitChanges();
}
catch(Exception ex)
{
string exp = ex.ToString();
}
หมายเหต
เราสามารถ insert ขอมลแบบ collection ไดโดยใช method ชอ InsertAllOnSubmit() ดง
ตวอยางตอไปน
mx.GetTable<EcUser>().InsertAllOnSubmit(EcUserCollection ecUsrCollection);
การแกไขขอมล var usrCcllection =
from usr in mx.GetTable<EcUser>()
where
usr.ClientNoOwner == "0001" &&
usr.ClientTypeNo == "01" &&
usr.ClientNo == "0001" &&
usr.ContactNo == "101049" &&
usr.UserName == "[email protected]"
select usr;
EcUser ecUsr = usrCcllection.ToList<EcUser>()[0];
ecUsr.UserStatus = "2";
ecUsr.UserCreateDate = DateTime.Now.AddDays(30);
try
{
mx.SubmitChanges();
}
catch (Exception ex)
{
string exp = ex.ToString();
} หมายเหต
เราสามารถ update ขอมลแบบ collection ไดโดยใช method ชอ InsertAllOnSubmit() ดง
ตวอยางตอไปน mx.GetTable<EcUser>().InsertAllOnSubmit(EcUserCollection ecUsrCollection); การลบขอมล var usrCcllection =
from usr in mx.GetTable<EcUser>()
where
usr.ClientNoOwner == "0001" &&
usr.ClientTypeNo == "01" &&
usr.ClientNo == "0001" &&
usr.ContactNo == "101049" &&
usr.UserName == "[email protected]"
select usr;
EcUser ecUsr = usrCcllection.ToList<EcUser>()[0];
ecUsr.UserStatus = "2";
ecUsr.UserCreateDate = DateTime.Now.AddDays(30);
try
{
mx.GetTable<EcUser>().DeleteOnSubmit(ecUsr);
mx.SubmitChanges();
}
catch (Exception ex)
{
string exp = ex.ToString();
} หมายเหต
เราสามารถ Delete ขอมลแบบ collection ไดโดยใช method ชอ DeleteAllOnSubmit()
ดงตวอยางตอไปน mx.GetTable<EcUser>().DeleteAllOnSubmit(EcUserCollection ecUsrCollection);
System Requirement
- Microsoft Framework 3.0 or higher
DLL Add-On
- ExtensionCore.dll
Namespace Declaration
- eCenter.Extensions - eCenter.Utilities.Runtime
- eCenter.Data.Linq - eCenter.Data.Mapper.Extensions
- System.Linq - System.Xml.Linq
- System.Data.Linq