zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    排序算法之冒泡排序的思想以及Java实现
    排序算法之希尔排序的思想以及Java实现
    c# npoi分批往excel追加数据
    c# Timer按月执行任务
    windows服务+定时任务(quartz.net)+nancy
    c# linq分组 lambda分组
    批量插入sql技巧
    解决windows server 2008R2自动关机
    c# DataGridView在使用DataSource时,只显示指定的列或禁止自动生成列
    c# mongodb时间类型字段保存时相差八个小时解决办法
  • 原文地址:https://www.cnblogs.com/Abbey/p/2106771.html
Copyright © 2011-2022 走看看