LINQ可以直接对DataSet进行查询,也可以用DataContext和Table(实现了ITable、IQueryable以及IEnumerable)。最紧绷的工作市定义一个对象关系映射(PRM),用于将C#实体类与数据库中的表映射起来。这不是很费事,因为可以使用SqlMetal命令行实用工具或LINQ to SQL类设计器来完成定义ORM这样的苦差事。
定义表对象
为了使用LINQ to SQL,你需要下面这些东西:
- 一个数据库
- System.Data.Linq的引用
- 一个DataContext实例,通过一个联接字符串连接到你的数据库
- 一个用于表示你的实体的类,还有TableAtrribute(用于将这个类与相应的表联系起来)
- 用ColumnAttribute将属性与表中的列映射起来
class Program
{
// 连接字符串
private static readonly string connectionString = @"Server=localhost;Database=Northwind;Trusted_Connection=True;";
static void Main(string[] args)
{
// 以连接字符串初始化数据上下文类
DataContext customerContext = new DataContext(connectionString);
// 获取表数据
Table<Customer> customers = customerContext.GetTable<Customer>();
// LINQ查询
var startsWithA = from customer in customers where customer.CustomerID[0] == 'A' select customer;
// 定义输出方式,Log属性使得LINQ可以将根据你的ORM所生成查询显示出来
customerContext.Log = Console.Out;
// 输出查询结果
Array.ForEach(startsWithA.ToArray(), c => Console.WriteLine(c));
}
}
/// <summary>Table标签索命这个Customer类映射的是Customers表</summary>
[Table(Name = "Customers")]
public class Customer
{
/// <summary>Column标签没有使用命名参数,当使用LINQ把修改后的数据保存回数据库时,元素不会写入数据库</summary>
[Column()]
public string CustomerID { get; set; }
[Column()]
public string CompanyName { get; set; }
[Column()]
public string ContactName { get; set; }
[Column()]
public string ContactTitle { get; set; }
[Column()]
public string Address { get; set; }
[Column()]
public string City { get; set; }
[Column()]
public string Region { get; set; }
[Column()]
public string PostalCode { get; set; }
[Column()]
public string Country { get; set; }
[Column()]
public string Phone { get; set; }
[Column()]
public string Fax { get; set; }
public override string ToString()
{
StringBuilder builder = new StringBuilder();
PropertyInfo[] props = this.GetType().GetProperties();
// using array for each
Array.ForEach(props.ToArray(),
prop => builder.AppendFormat("{0} : {1}", prop.Name, prop.GetValue(this, null) == null ? "<empty>\n" : prop.GetValue(this, null).ToString() + "\n"));
return builder.ToString();
}
}
将类映射到表
映射到数据库表的类叫做实体。对LINQ而言,实体都要使用TableAttribute和ColumnAttribute进行修饰。ColumnAttribute可以引用再任何字段或属性上(private, public, internal),不过当LINQ把要修改保存回数据库时,实体中只有那些带有ColumnAttribute的元素才会被持久化。
ColumnAttribute的命名参数
命名属性 | 说明 |
---|---|
AutoSync | 指示何时将该列与数据库进行同步 |
CanBeNull | 指示该列是否可以包含null值 |
DbType | 表示DbType枚举值之一 |
Expression | 说明计算列时如何产生的 |
IsDbGenerated | 指示该列是否时自动生成的,比如自动生成的主键列 |
IsDiscriminator | 指示该列是否包含LINQ to SQL 继承层次结构的鉴别器值 |
IsPrimaryKey | 指示该列是否是主键 |
IsVersion | 指示该列是否是数据库时间戳或版本号 |
Name | 显示列名称 |
Storage | 标识当前实体类中用于存储该列的字段 |
UpdateCheck | 指示如何检测并发冲突 |
[Table(Name = "Customers")]
public class Customers
{
private string customerID;
private string companyName;
private string contactName;
private string contactTitle;
private string address;
private string city;
private string region;
private string postalCode;
private string country;
private string phone;
private string fax;
[Column(Name = "CustomerID", Storage = "customerID", DbType = "NChar(5)", CanBeNull = false)]
public string CustomerID
{
get { return customerID; }
set { customerID = value; }
}
[Column(Name = "CompanyName", Storage = "companyName", DbType = "NVarChar(40)", CanBeNull = true)]
public string CompanyName
{
get { return companyName; }
set { companyName = value; }
}
[Column(Name = "ContactName", Storage = "contactName", DbType = "NVarChar(30)")]
public string ContactName
{
get { return contactName; }
set { contactName = value; }
}
[Column(Name = "ContactTitle", Storage = "contactTitle", DbType = "NVarChar(30)")]
public string ContactTitle
{
get { return contactTitle; }
set { contactTitle = value; }
}
[Column(Name = "Address", Storage = "address", DbType = "NVarChar(60)")]
public string Address
{
get { return address; }
set { address = value; }
}
[Column(Name = "City", Storage = "city", DbType = "NVarChar(15)")]
public string City
{
get { return city; }
set { city = value; }
}
[Column(Name = "Region", Storage = "region", DbType = "NVarChar(15)")]
public string Region
{
get { return region; }
set { region = value; }
}
[Column(Name = "PostalCode", Storage = "postalCode", DbType = "NVarChar(10)")]
public string PostalCode
{
get { return postalCode; }
set { postalCode = value; }
}
[Column(Name = "Country", Storage = "country", DbType = "NVarChar(15)")]
public string Country
{
get { return country; }
set { country = value; }
}
[Column(Name = "Phone", Storage = "phone", DbType = "NVarChar(24)")]
public string Phone
{
get { return phone; }
set { phone = value; }
}
[Column(Name = "Fax", Storage = "fax", DbType = "NVarChar(24)")]
public string Fax
{
get { return fax; }
set { fax = value; }
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
PropertyInfo[] props = this.GetType().GetProperties();
// using array for each
Array.ForEach(props.ToArray(),
prop => builder.AppendFormat("{0} : {1}", prop.Name,prop.GetValue(this, null) == null ? "<empty>\n" :prop.GetValue(this, null).ToString() + "\n"));
return builder.ToString();
}
}
查看由LINQ生成的查询文本
如果将一个TextWrite赋值给DataContext.Log,则DataContext就会在LINQ to SQL提供者工作时显示出相关的信息。如果只想看SQL文本的话,可以通过DataContext.GetCommand()方法获取。GetCommand将会返回一个DBCommand,你可以从这里获取SQL。
class Program
{
private static readonly string connectionString = "Server=localhost;Database=Northwind;Trusted_Connection=True;";
static void Main(string[] args)
{
DataContext context = new DataContext(connectionString);
Table<OrderDetail> details = context.GetTable<OrderDetail>();
// 获取SQL语句
Console.WriteLine("SELECT: {0}", context.GetCommand(details).CommandText);
}
}
[Table(Name = "Order Details")]
public class OrderDetail
{
[Column()]
public int OrderID { get; set; }
[Column()]
public int ProductID { get; set; }
[Column()]
public decimal UnitPrice { get; set; }
[Column()]
public Int16 Quantity { get; set; }
[Column()]
public float Discount { get; set; }
public override string ToString()
{
StringBuilder builder = new StringBuilder();
PropertyInfo[] props = this.GetType().GetProperties();
// using array for each
Array.ForEach(props.ToArray(), prop =>
builder.AppendFormat("{0} : {1}", prop.Name,
prop.GetValue(this, null) == null ? "<empty>\n" :
prop.GetValue(this, null).ToString() + "\n"));
return builder.ToString();
}
}
结果
SELECT: SELECT [t0].[OrderID], [t0].[ProductID], [t0].[UnitPrice], [t0].[Quantity], [t0].[Discount]
FROM [Order Details] AS [t0]
通过DataContext对象连接关系型数据
可以通过继承DataContext, 并在新类中嵌入诸如连接字符串值类的一些信息.
public class Northwind : DataContext
{
private static readonly string connectionString = "Data Source=BUTLER;Initial Catalog=Northwind;Integrated Security=True";
public Northwind() : base(connectionString){ }
}
class Program
{
static void Main(string[] args)
{
Northwind context = new Northwind();
Table<OrderDetail> details = context.GetTable<OrderDetail>();
var results = from detail in details where detail.OrderID == 10248 select detail;
Array.ForEach(results.ToArray(), d => Console.WriteLine(d));
}
}
查询数据集
LINQ对DataSet的支持主要时通过DataRowExtensions和DataTableExtensions这两个类实现的.DataRowExtensions含有扩展方法Field和SetField,而DataTableExtensions类则含由拓展方法AsDataView\AsEnumerable以及CopyToDataTable.
从DataTable中获取数据
LINQ to DataSet的关键字就是使用DataRowExtensions和DataTableExtensions中的那些方法.表扩展方法将产生一个可查询的序列,而行扩展方法则提供对数据的字段级方法.
class Program
{
private static readonly string connectionString = "Server=localhost;Database=Northwind;Trusted_Connection=True;";
static void Main(string[] args)
{
// 获取数据
DataSet data = new DataSet();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand("SELECT * FROM Suppliers", connection);
command.CommandType = CommandType.Text;
SqlDataAdapter adapter = new SqlDataAdapter(command);
adapter.Fill(data, "Suppliers");
}
DataTable supplierTable = data.Tables["Suppliers"];
// 使用LINQ从Table中查询数据
IEnumerable<DataRow> suppliers = from supplier in supplierTable.AsEnumerable() select supplier;
Console.WriteLine("Supplier Information");
foreach (DataRow row in suppliers)
{
Console.WriteLine(row.Field<string>("CompanyName"));
Console.WriteLine(row.Field<string>("City"));
Console.WriteLine();
}
}
}
查询DataTable时使用Where子句
class Program
{
private static readonly string connectionString = "Server=localhost;Database=Northwind;Trusted_Connection=True;";
static void Main(string[] args)
{
string sql = "SELECT * FROM [Order Details] od INNER JOIN Products p on od.ProductID = od.ProductID";
// 获取数据
DataSet data = new DataSet();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand(sql, connection);
command.CommandType = CommandType.Text;
SqlDataAdapter adapter = new SqlDataAdapter(command);
adapter.Fill(data);
}
DataTable detailTable = data.Tables[0];
// 使用LINQ从Table中查询数据
IEnumerable<DataRow> details = from detail in detailTable.AsEnumerable()
where detail.Field<float>("Discount") > 0.10f
select detail;
details.Dump();
Console.WriteLine("Big - discount orders");
foreach (DataRow row in details)
{
Console.WriteLine(row.Field<int>("OrderID"));
Console.WriteLine(row.Field<string>("ProductName"));
Console.WriteLine(row.Field<decimal>("UnitPrice"));
Console.WriteLine(row.Field<float>("Discount"));
Console.WriteLine();
}
}
}
在DataSet上定义联接
LINQ to DataSet也是支持联接的,不过需要在查询中使用数据表扩展和数据行扩展.可以使用数据表扩展获取序列,然后用数据行扩展获取字段.
class Program
{
private static readonly string connectionString = "Server=localhost;Database=Northwind;Trusted_Connection=True;";
static void Main(string[] args)
{
// 单个调用中向SQL Server发送多个select语句,只需要将这些查询用封号隔开即可
const string sql = "SELECT * FROM Orders;SELECT * FROM [Order Details];";
DataSet data = new DataSet();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command =
new SqlCommand(sql, connection);
command.CommandType = CommandType.Text;
SqlDataAdapter adapter = new SqlDataAdapter(command);
adapter.Fill(data);
}
DataTable orders = data.Tables[0];
DataTable orderDetails = data.Tables[1];
var orderResults =
from order in orders.AsEnumerable()
join detail in orderDetails.AsEnumerable()
on order.Field<int>("OrderID")
equals detail.Field<int>("OrderID")
select new
{
OrderID = order.Field<int>("OrderID"),
CustomerID = order.Field<string>("CustomerID"),
ProductID = detail.Field<int>("ProductID"),
UnitPrice = detail.Field<decimal>("UnitPrice"),
Quantity = detail.Field<Int16>("Quantity"),
Discount = detail.Field<float>("Discount")
};
Console.WriteLine("Orders & Details");
foreach (var result in orderResults)
{
Console.WriteLine("Order ID: {0}", result.OrderID);
Console.WriteLine("Customer ID: {0}", result.CustomerID);
Console.WriteLine("Product ID: {0}", result.ProductID);
Console.WriteLine("Unit Price: {0}", result.UnitPrice);
Console.WriteLine("Quantity: {0}", result.Quantity);
Console.WriteLine("Discount: {0}", result.Discount);
Console.WriteLine();
}
}
}
SqlMetal : 使用实体类生成工具
SqlMetal用于生成.dbml(数据库标记语言, Database Markup Language)文件,也可用用LINQ to SQL生成ORM源文件…dbml文件是一种扩展标记语言,它含有用于描述架构定义的信息.
下面这行梦灵用SqlMetal来生成一个DataContext,以及Northwind数据库中的全部实体类(生成一个DataContext类,并为该数据库中的每个表生成一个实体类).
SqlMetal /server:butler /database:Northwind /code:northwind.cs
SqlMetal有许多选项.服务器名, 数据库名, 用户名, 密码, 联接字符串以及超时值都可以用作命令选项.还有一些命令行选项用于提取视图\函数和存储过程.其输出可以被创建为映射文件, dbml文件或源代码文件.可以指定生成的代码语言,也可以让该工具从源代码文件的拓展名来推断出具体的代码语言.除了语言选项之外,命名空间\数据上下文类的名称, 实体积累,实体类名的单复数形式,以及类是否是可序列化的,这些选项也可以用在命令行中.
SqlMetal 帮助
SqlMetal [选项] [<输入文件>]
为 .NET Framework 的 LINQ to SQL 组件生成代码和映射。SqlMetal 能够:
- 依据数据库生成源代码及映射属性或映射文件。
- 依据数据库生成中间 dbml 文件以进行自定义。
- 依据 dbml 文件生成代码及映射属性或映射文件。
选项:
/server:<名称> 数据库服务器名称。
/database:<名称> 服务器上的数据库目录。
/user:<名称> 登录用户 ID (默认值: 使用 Windows 身份验证)。
/password:<密码> 登录密码(默认值: 使用 Windows 身份验证)。
/conn:<连接字符串> 数据库连接字符串。不能将连接字符串与 /server、/database、/user 或 /password 选项一起使用。
/timeout:<秒数> 要在 SqlMetal 访问数据库时使用的超时值(默认值: 0,表示无限期)。
/views 提取数据库视图。
/functions 提取数据库函数。
/sprocs 提取存储过程。
/dbml[:文件] 输出为 dbml。不能与 /map 选项一起使用。
/code[:文件] 输出为源代码。不能与 /dbml 选项一起使用。
/map[:文件] 生成映射文件而不是属性。不能与 /dbml 选项一起使用。
/language:<语言> 源代码语言: VB 或 C# (默认值: 派生自代码文件名的扩展名)。
/namespace:<名称> 生成的代码的命名空间(默认值: 无命名空间)。
/context:<类型> 数据上下文类的名称(默认值: 派生自数据库名称)。
/entitybase:<类型> 生成的代码中的实体类的基类(默认值: 实体没有基类)。
/pluralize 使用英语语言规则自动设置类和成员名称的单复数形式。
/serialization:<选项> 生成可序列化的类: None 或 Unidirectional (默认值: None)。
/provider:<类型> 提供程序类型: SQLCompact、SQL2000、SQL2005 或 SQL2008。(默认值: 提供程序是在运行时确定的)。
<输入文件> 可以是 SqlExpress mdf 文件、SqlCE sdf 文件或 dbml 中间文件。
通过 SqlServer 创建代码:
SqlMetal /server:myserver /database:northwind /code:nwind.cs /namespace:nwind
通过 SqlServer 生成中间 dbml 文件:
SqlMetal /server:myserver /database:northwind /dbml:northwind.dbml /namespace:nwind
通过 dbml 生成包含外部映射的代码:
SqlMetal /code:nwind.cs /map:nwind.map northwind.dbml
通过 SqlCE sdf 文件生成 dbml:
SqlMetal /dbml:northwind.dbml northwind.sdf
通过 SqlExpress 本地服务器生成 dbml:
SqlMetal /server:.\sqlexpress /database:northwind /dbml:northwind.dbml
通过在命令行中使用连接字符串生成 dbml:
SqlMetal /conn:"server='myserver'; database='northwind'" /dbml:northwind.dbml
使用LINQ to SQL 类设计器
LINQ to SQL是类设计器是Visual Studio的一个继承组件.类设计器是 Visual Studio的一种设计器.设计器是可以通过某种特定方式与Visual Studio进行交互的程序.这里,设计器能够将表, 存储过程以及函数拖放到一个图形化用户界面上,然后生成一个数据库标记语言文件,并将这些元素转换为XML和源代码.类设计器类似于SqlMetal, 只不过它是一种可视化的方式.