【笔记】【LINQ编程技术内幕】第十三章 使用LINQ查询关系型数据

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, 只不过它是一种可视化的方式.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhy29563

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值