Pro LINQ 之三:LINQ to DataSet

写在前面

将LINQ to DataSet单独放一篇,是因为随后的LINQ to SQL默认只支持MS SQL Server。只有LINQ to DataSet能在没有相应Data Provider帮助的情况下,与其他数据库平台进行交互。而且LINQ to DataSet中的许多基本概念,是引导我们深入LINQ to SQL以及Entity Framework的基础。


P341 DataRow的集合运算符

通过LINQ与数据库交互,仍需要LINQ的三个基本要素:DataTable是序列,DataRow是元素,以及相关的操作符。

但是DataRow与Object的显著区别在于如何唯一性地标识一个DataRow。Object可以通过GetHashCode()来保证其被唯一性地识别,而DataRow只会导致引用Reference间的比较,而这并不是我们所期望的。因为不同时刻我们从数据库中加载的同一条DataRow,可能对应完全不同的引用。为此,在DataRow的LINQ运算符中,出现了一个默认的比较器DataRowComparer.Default。使用的方式与LINQ to Objects一致:

DataTable dt = new DataTable();
IEnumerable<DataRow> except = dt.AsEnumerable().Except(subtable, DataRowComparer.Default);

有了DataRowComparer.Default的帮助,当我们把IQueryable接口的DataTable利用AsEnumerable()转换为IEnumerable<>后,即可采取类似LINQ to Objects的运算符进行操作了。如果没有DataRowComparer.Default,则会在DataRow的对象筛选时得到出乎意料的结果。

需要说明的是,DataRowComparer<T>是对应的泛型。可以自由定制自己的DataRow比较子。

P353 DataRow的Field运算符

问题来源于下面这样的一种情形:

string anthonysClass = (from s in seq1
                        where s.Field<string>("Name") == "Anthony Adams"
                        from c in seq2
                        where c["Id"] == s["Id"]
                        select (string) c["Class"])
                        .SingleOrDefault<string>();

由于DataRow[列名]的引用,本来是int类型的列值返回后被装箱成为object,其中的c["Id"] == s["Id"]将是两个object之间的比较,从而导致结果错误。当我们将比较条件修改为(int) c["Id"] == (int) s["Id"]之后,才能返回正确的结果。但从另一个方面考虑,当该字段值为DBNull时(int)c["Id"]这样的强制类型转换便不再有效,因此引入了Field<>运算符。需要说明的是,Field<>取出的值有可能是null。

其存在三种引用方式:

row.Field<字段值类型>(列序号) row.Field<int>(0)
row.Field<字段值类型>(列名) row.Field<int>(“Id”)
row.Field<字段值类型>(Column引用) row.Field<int>(dt.Columns[0])

同时,还引入了Field<>的DataRowVersion,这与ADO.NET中的DataRowState是相互呼应的。

Original 取原始值
Current 取当前值
Proposed 取建议值
Default Added、Modified或Current=>Delete
Detached=>Proposed

例如这样:

int id = (from s in seq1
      where s.Field<string>("Name") == "Anthony Adams"
      select s.Field<int>("Id", DataRowVersion.Current))
      .SingleOrDefault<int>();

P362 DataRow的SetField<T>运算符

SetField<>(列, 值),其中列的引用方式同Field<>。不同于Field<>的,是null值将可能引发DBNull异常。

特别是对于string类型的列,由于不支持string? name这样的可空声明,因此要特别注意处理,避免陷入DBNull异常。

(from s in seq1
 where s.Field<string>("Name") == "Michael Bluth"
 select s).Single<DataRow>().SetField("Name", "Tony Wonder");

P365 DataTable相关运算符

AsEnumerable()相对简单,稍微复杂一点的是CopyToDataTable()。后者有一个类似于DataRowState的LoadOption选择方式。每个列,都有Current与Original两个版本的数据。这个LoadOption决定哪个版本的数据被拷贝到新的DataTable中。

OverwriteChanges Current与Original都拷贝
PreserveChanges 只拷贝Original
Upsert 只拷贝Current

一个需要考虑的情形是,在没有定义新表主键的情况下,在利用DataTable.AcceptChanges()将所有DataRow的State复位之前,如果象下面这样执行2次CopyToDataTable(),将会导致DataRow被重复添加。其中第2次拷贝的DataRow将没有Original版本。

Console.WriteLine("Before upserting DataTable:");
foreach (DataRow dataRow in newTable.AsEnumerable())
{
    Console.WriteLine("Student Id = {0} : original {1} : current {2}",
    dataRow.Field<int>("Id"),
    dataRow.Field<string>("Name", DataRowVersion.Original),
    dataRow.Field<string>("Name", DataRowVersion.Current));
}

(from s in dt1.AsEnumerable()
where s.Field<string>("Name") == "Anthony Adams"
select s).Single<DataRow>().SetField("Name", "George Oscar Bluth");

dt1.AsEnumerable().CopyToDataTable(newTable, LoadOption.Upsert);

Console.WriteLine("{0}After upserting DataTable:", System.Environment.NewLine);
foreach (DataRow dataRow in newTable.AsEnumerable())
{
    Console.WriteLine("Student Id = {0} : original {1} : current {2}",
    dataRow.Field<int>("Id"),
    dataRow.HasVersion(DataRowVersion.Original) ?
    dataRow.Field<string>("Name", DataRowVersion.Original) : "-does not exist-",
    dataRow.Field<string>("Name", DataRowVersion.Current));
}

运行结果:

---------------------------------------------------------------
Before upserting DataTable:
Student Id = 1 : original Joe Rattz : current Joe Rattz
Student Id = 7 : original Anthony Adams : current Anthony Adams
Student Id = 13 : original Stacy Sinclair : current Stacy Sinclair
Student Id = 72 : original Dignan Stephens : current Dignan Stephens
After upserting DataTable:
Student Id = 1 : original Joe Rattz : current Joe Rattz
Student Id = 7 : original Anthony Adams : current Anthony Adams
Student Id = 13 : original Stacy Sinclair : current Stacy Sinclair
Student Id = 72 : original Dignan Stephens : current Dignan Stephens
Student Id = 1 : original -does not exist- : current Joe Rattz
Student Id = 7 : original -does not exist- : current George Oscar Bluth
Student Id = 13 : original -does not exist- : current Stacy Sinclair
Student Id = 72 : original -does not exist- : current Dignan Stephens
---------------------------------------------------------------

因此,指定新表的主键是必要的。就象下面这样:

DataTable newTable = dt1.AsEnumerable().CopyToDataTable();
newTable.PrimaryKey = new DataColumn[] { newTable.Columns[0] };

不知道列的基本特征、约束等会否一并被Copy?待解!

P373 LINQ to 类型化DataSet

类型化DataSet,基于编译时的类型检查和直观的表、列引用语法等优势,曾经是NET 2.0时代数据库应用的利器。但受限于其Cache的特性,和缺乏一致的查询机制,在N层应用中上下穿梭的能力有限,逐渐被新兴的LINQ与Entity所取代。但在小型的数据库应用中,DataSet仍有一定的生存空间。

string name = studentsDataSet.Students
    .Where(student => student.Id == 7)
    .Single().Name;

特别要提的是,类SQL语法的形式与扩展方法调用的形式相比较,仍是后者的效能胜出。


[LINQ] Pro LINQ 之四:LINQ to SQL

原文地址:https://www.cnblogs.com/Abbey/p/2106771.html