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

    技术2022-07-12  79

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

    Processed: 0.013, SQL: 10