C# 2008 学习笔记 - LINQ to ADO.NET(三)- LINQ to SQL
Posted on 2008-02-22 15:59 sunrack 阅读(1303) 评论(5) 编辑 收藏 举报LINQ to SQL 的主要目标是实现关系数据库和编程逻辑的一致性。例如,不是用字符串来表示查询,而是使用强类型的LINQ查询,再比如,不是把数据库看作记录流,而是可以使用面向对象的编程技术。LINQ可以将数据访问直接集成到代码中,不再需要手动去写很多的类和类库来封装ADO.NET提供的对象。
使用LINQ to SQL时,不再需要使用ADO.NET提供的对象,如SqlConnection, SqlCommand, or SqlDataAdapter。LINQ to SQL entity classes 和 DataContext 来完成所有的数据库访问操作CRUD(create, remove, update, and delete),定义 transactional contexts, 创建新的 database
entities (或者 entire databases),调用存储过程,以及其他的数据库相关的操作。
同时,LINQ to SQL 类型 和 ADO.NET是完全集成的。比如,DataContext 的一个构造函数,就是用 IDbConnection 类型的对象作为参数。所以,现有的ADO.NET 数据访问层类库可以和 LINQ to SQL 进行集成。 用微软的话说,LINQ to SQL 是ADO.NET family的新增成员。
一、Entity Classes 简介
要使用 LINQ to SQL,第一步就是定义 Entity Classes。 简单的说,Entity Classes 代表数据库中需要访问的关系数据,从编成角度讲,Entity Classes是用LINQ to SQL 属性(如[Table] 和 [Column]))标识,映射到数据库中物理表上的类。大部分 LINQ to SQL 属性 都在 System.Data.Linq.Mapping 中定义。
二、DataContext 简介
DataContext 负责将 LINQ 查询表达式 翻译为 SQL 查询语句,并负责和数据库的通讯。有点像 ADO.NET connection 对象,因为它也需要一个连接字符串。但是,与connection 对象不同的是,DataContext 还包括了很多函数,可以将LINQ查询结果集映射到定义的 Entity Classes 上。
另外,DataContext 使用工厂模式来获取定义的 Entity Classes 实例,一旦得到该实例,就可以对立的进行各种数据操纵,然后,把修改后的实例交给DataContext 去处理,这点上,又有点像 ADO.NET data adapter 类型。
三、LINQ to SQL实例
首先手工创建 Entity Classe,需要引用 System.Data.Linq.Mapping 和 System.Data.Linq
public class Inventory
{
[Column]
public string Make;
[Column]
public string Color;
[Column]
public string PetName;
// Identify the primary key.
[Column(IsPrimaryKey = true)]
public int CarID;
public override string ToString()
{
return string.Format("ID = {0}; Make = {1}; Color = {2}; PetName = {3}",
CarID, Make.Trim(), Color.Trim(), PetName.Trim());
}
}
注意:
1、TableAttribute 和 ColumnAttribute 都提供了 Name 属性,这样就可以不用直接映射到物理数据库上,降低了耦合度。
2、ColumnAttribute 的 IsPrimaryKey 属性可以定义主键
3、每个字段可以使用标准的属性语法,定义私有变量和属性,也可以使用自动属性(automatic properties)定义。
4、Entity Classe 中可以定义不和数据表相关的字段。LINQ在运行时,只使用LINQ to SQL 属性标记了的属性去交换数据。
下面进行调用,
{
const string cnStr =
@"Data Source=(local)\SQLEXPRESS;Initial Catalog=AutoLot;" +
"Integrated Security=True";
static void Main(string[] args)
{
Console.WriteLine("***** LINQ to SQL Sample App *****\n");
// Create a DataContext object.
DataContext db = new DataContext(cnStr);
// Now create a Table<> type.
Table<Inventory> invTable = db.GetTable<Inventory>();
// Show all data using a LINQ query.
Console.WriteLine("-> Contents of Inventory Table from AutoLot database:\n");
foreach (var car in from c in invTable select c)
Console.WriteLine(car.ToString());
Console.ReadLine();
}
}
需要使用连接字符串来初始化DataContext,可以保存在 App.config 或者 使用 SqlConnectionStringBuilder 去构造。
四、强类型的 DataContext
{
public Table<Inventory> Inventory;
public AutoLotDatabase(string connectionString)
: base(connectionString){}
}
调用
{
Console.WriteLine("***** LINQ to SQL Sample App *****\n");
// Create an AutoLotDatabase object.
AutoLotDatabase db = new AutoLotDatabase(cnStr);
// Note we can now use the Inventory field of AutoLotDatabase.
Console.WriteLine("-> Contents of Inventory Table from AutoLot database:\n");
foreach (var car in from c in db.Inventory select c)
Console.WriteLine(car.ToString());
Console.ReadLine();
}
注意,这里 AutoLotDatabase 并没有直接创建 Table<T> 的实例,在运行时,在迭代访问LINQ查询结果集时,DataContext才会在后台隐式的创建Table<T>实例。
可以进行任何的LINQ查询
{
Console.WriteLine("***** Only BMWs *****\n");
// Get the BMWs.
var bimmers = from s in db.Inventory
where s.Make == "BMW"
orderby s.CarID
select s;
foreach (var c in bimmers)
Console.WriteLine(c.ToString());
}
五、[Table] 和 [Column] 属性
LINQ To SQL 属性(attributes)有一系列的properties,用来告诉 LINQ to SQL runtime engine 如何处理这些被修饰的对象。
1、[Table]
只有一个属性 : Name, 可以减弱 entity class 的名称与物理表之间的耦合。如果不指定 Name 属性,则默认 entity class 的名称与物理表名称相同。
2、[Column]
ColumnAttribute Property |
Meaning in Life |
CanBeNull |
This property indicates that the column can contain null values. |
DbType |
LINQ to SQL will automatically infer the data types to pass to the database engine based on declaration of your field data. Given this, it is typically only necessary to set DbType directly if you are dynamically creating databases using the CreateDatabase() method of the DataContext type. |
IsDbGenerated |
This property establishes that a field’s value is autogenerated by the database. |
IsVersion |
This property identifies that the column type is a database timestamp or a version number. Version numbers are incremented and timestamp columns are updated every time the associated row is updated. |
UpdateCheck |
This property controls how LINQ to SQL should handle database conflicts via optimistic concurrency. |
IsPrimaryKey |
|
六、使用 SqlMetal.exe 生成 Entity Classes
sqlmetal.exe Command-Line Option |
Meaning in Life |
/server |
Specifies the server hosting the database |
/database |
Specifies the name of the database to read metadata from |
/user |
Specifies user ID to log in to the server |
/password |
Specifies password to log in to the server |
/views |
Informs sqlmetal.exe to generate code based on existing database views |
/functions |
Informs sqlmetal.exe to extract database functions |
/sprocs |
Informs sqlmetal.exe to extract stored procedures |
/code |
Informs sqlmetal.exe to output results as C# code (or as VB, if you set the /language flag) |
/language |
Specifies the language used to defined the generated types |
/namespace |
Specifies the namespace to define the generated types |
例如:
/code:autoLotDB.cs /sprocs
自动生成的 Entity Classes 都实现了两个接口:INotifyPropertyChanging 和 INotifyPropertyChanged,即两个事件 PropertyChanging 和 PropertyChanged ,这两个事件与 System.ComponentModel 中定义的 PropertyChangedEventHandler 代理一起配合使用。PropertyChangedEventHandler 可以调用任何把一个Object对象和一个PropertyChangedEventArgs对象作为参数的函数。
每个 Entity Classes 都包含两个成员:
public partial class Inventory : INotifyPropertyChanging, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
}
另外,每个 Entity Classes 的属性的 set 部分,都调用了相关的监听器(listener.)。
public string PetName
{
get
{
return this._PetName;
}
set
{
if ((this._PetName != value))
{
this.OnPetNameChanging(value);
this.SendPropertyChanging();
this._PetName = value;
this.SendPropertyChanged("PetName");
this.OnPetNameChanged();
}
}
}
这里,OnPetNameChanging() 和 OnPetNameChanged() 都是部分方法(partial methods),
partial void OnPetNameChanging(string value);
partial void OnPetNameChanged();
可以根据需要进行实现。否则就会在编译时删除。
七、给 Entity Classes 定义关系
自动生成的代码同时也生成了关系。使用到了 EntitySet<T> 类型。父表将子表包含为引用属性(property references),这个属性使用 [Association] 来建立连个表中字段之间的联系(association relationship),例如
public partial class Customers :
INotifyPropertyChanging, INotifyPropertyChanged
{
private EntitySet<Orders> _Orders;
[Association(Name="FK_Orders_Customers", Storage="_Orders",
OtherKey="CustID", DeleteRule="NO ACTION")]
public EntitySet<Orders> Orders
{
get { return this._Orders; }
set { this._Orders.Assign(value); }
}
}
这样,就可以在运行时,通过 OtherKey 从Customers 浏览到 Orders。The EntitySet<T> 用来表示 一对多的关系。
八、强类型 DataContext
自动生成的 DataContext,继承自DataContext,每个表都作为Table<T>类型的属性。这个类的一个构造函数,需要IDbConnection参数。
同样,这个类也用于和存储过程交互,假设使用了/sprocs参数,则会看到自动生成了一个方法GetPetName()
[return: Parameter(DbType="Int")]
public int GetPetName([Parameter(DbType="Int")] System.Nullable<int> carID,
[Parameter(DbType="Char(10)")] ref string petName)
{
IExecuteResult result = this.ExecuteMethodCall(this,
((MethodInfo)(MethodInfo.GetCurrentMethod())), carID, petName);
petName = ((string)(result.GetParameterValue(1)));
return ((int)(result.ReturnValue));
}
九、调用自动生成的代码
{
const string cnStr =
@"Data Source=(local)\SQLEXPRESS;Initial Catalog=AutoLot;" +
"Integrated Security=True";
static void Main(string[] args)
{
Console.WriteLine("***** More Fun with LINQ to SQL *****\n");
AutoLot carsDB = new AutoLot(cnStr);
InvokeStoredProc(carsDB);
Console.ReadLine();
}
private static void InvokeStoredProc(AutoLot carsDB)
{
int carID = 0;
string petName = "";
Console.Write("Enter ID: ");
carID = int.Parse(Console.ReadLine());
// Invoke stored proc and print out the petname.
carsDB.GetPetName(carID, ref petName);
Console.WriteLine("Car ID {0} has the petname: {1}",
carID, petName);
}
}
上面直接调用了存储过程
{
int custID = 0;
Console.Write("Enter customer ID: ");
custID = int.Parse(Console.ReadLine());
var customerOrders =
from cust in carsDB.Customers
from o in cust.Orders
where cust.CustID == custID
select new { cust, o };
Console.WriteLine("***** Order Info for Customer ID: {0}. *****", custID);
foreach (var q in customerOrders)
{
Console.WriteLine("{0} {1} is order ID # {2}.",
q.cust.FirstName.Trim(),
q.cust.LastName.Trim(),
q.o.OrderID);
Console.WriteLine("{0} bought Car ID # {1}.",
q.cust.FirstName.Trim(), q.o.CarID);
}
}
实际上可以看出,查询表达式其实代表
[t1].[OrderID], [t1].[CarID], [t1].[CustID] AS [CustID2]
FROM [Customers] AS [t0], [Orders] AS [t1]
WHERE ([t0].[CustID] = @p0) AND ([t1].[CustID] = [t0].[CustID])
十、使用 Visual Studio 2008 生成 Entity Classes
Project -> Add New Item 然后新建 LINQ to SQL Classes(*.dbml)。
然后可以直接在 Server Explorer 中拖拽表格到设计界面上,生成的代码和 sqlmetal.exe 生成的是一样的。同时还会生成一个 app.config 文件来保存连接字符串。
十一、添加
{
Console.WriteLine("***** Adding 2 Cars *****");
int newCarID = 0;
Console.Write("Enter ID for Betty: ");
newCarID = int.Parse(Console.ReadLine());
// Add a new row using "longhand" notation.
Inventory newCar = new Inventory();
newCar.Make = "Yugo";
newCar.Color = "Pink";
newCar.PetName = "Betty";
newCar.CarID = newCarID;
ctx.Inventories.InsertOnSubmit(newCar);
ctx.SubmitChanges();
Console.Write("Enter ID for Henry: ");
newCarID = int.Parse(Console.ReadLine());
// Add another row using "shorthand" object init syntax.
newCar = new Inventory { Make = "BMW", Color = "Silver",
PetName = "Henry", CarID = newCarID };
ctx.Inventories.InsertOnSubmit(newCar);
ctx.SubmitChanges();
}
十二、更新
{
Console.WriteLine("***** Updating color of 'Betty' *****");
// Update Betty's color to light pink.
var betty = (from c in ctx.Inventories
where c.PetName == "Betty"
select c).First();
betty.Color = "Green";
ctx.SubmitChanges();
}
十三、删除
{
int carToDelete = 0;
Console.Write("Enter ID of car to delete: ");
carToDelete = int.Parse(Console.ReadLine());
// Remove specified car.
ctx.Inventories.DeleteOnSubmit((from c in ctx.Inventories
where c.CarID == carToDelete
select c).First());
ctx.SubmitChanges();
}