c# - CopyToDataTable : Why exactly does it throw an error if one field is NULL, 以及如何修复它?

标签 c# .net linq

问题:

我正在使用 Linq.Dynamic 的修改版本根据 ajax 请求对数据表进行排序(请参见下面的代码)。 到目前为止,它曾经工作正常。

但是,我有一个问题:

如果我有一个包含 NULL 值的数据表,即使只有一行中只有一个字段为 NULL,我也会得到这两个异常:

Object must be of type "string"

如果相邻的几个值为NULL:

At least one object must implement IComparable

这是我的代码

using System.Linq;
using System.Data;
using System.Linq.Dynamic;


   // Pre:
   // sidx: Sort Field
   // sord: Sort order ["ASC", "DESC"]
   // page: current page number
   // rows: pagesize in rows
   public static string TestPaging(string sidx, string sord, string page, string rows)
   {
        string strReturnValue = null;

        using (System.Data.DataTable dtAllData = GetDataTable())
        {

            IQueryable<System.Data.DataRow> iqOrderedPagedData = null;


            if (string.IsNullOrEmpty(sidx) || string.IsNullOrEmpty(sord))
                iqOrderedPagedData = dtAllData.AsEnumerable().AsQueryable();
            else
                iqOrderedPagedData = dtAllData.AsEnumerable().AsQueryable().OrderBy(sidx + " " + sord);


            Int32 iPageSize = string.IsNullOrEmpty(rows) ? 100 : System.Convert.ToInt32(rows);
            Int32 iPageIndex = System.Convert.ToInt32(page) - 1;

            iqOrderedPagedData = iqOrderedPagedData.Skip(iPageIndex * iPageSize).Take(iPageSize);


            //using (System.Data.DataTable dtOrderedPagedData = MyCopyToDataTable(iqOrderedPagedData))
            using (System.Data.DataTable dtOrderedPagedData = iqOrderedPagedData.CopyToDataTable())
            {
                cjqGrid jqGrid = new cjqGrid();
                //jqGrid.total = dtAllData.Rows.Count / iPageSize + 1;
                jqGrid.total = (int)Math.Ceiling((float)dtAllData.Rows.Count / (float)iPageSize);
                jqGrid.page = iPageIndex + 1;
                jqGrid.records = dtAllData.Rows.Count;
                jqGrid.data = dtOrderedPagedData;
                strReturnValue = null; // Serialize(jqGrid, true);
                jqGrid = null;
            } // End Using dtOrderedPagedData 

        } // End Using dtAllData

        //Response.ContentType = "application/json";
        return strReturnValue;
    }


TestPaging("USR_Domain", "desc", "1", "10");

问题似乎是扩展方法CopyToDataTable ,在行:

if (!e.MoveNext ())

这使得它对表进行排序,这意味着它调用了类中的函数 Compare System.Linq.SortSequenceContext

在这一行抛出错误

comparison = comparer.Compare(keys[first_index], keys[second_index]);

这是来自 mono 的版本,带有我的修复程序,使该 mono 方法真正起作用。
但是,该错误也出现在普通的旧版 MS .NET 4.0 中。
(我需要单声道将我的 Linq 使用方法反向移植到 .NET 2.0)

public static DataTable CopyToDataTable<T> (this IEnumerable<T> source)
            where T : DataRow
        {
            DataTable dt = new DataTable ();
            IEnumerator<T> e = source.GetEnumerator ();
            if (!e.MoveNext ())
                throw new InvalidOperationException ("The source contains no DataRows");
            foreach (DataColumn col in e.Current.Table.Columns)
                dt.Columns.Add (new DataColumn (col.ColumnName, col.DataType, col.Expression, col.ColumnMapping));
            CopyToDataTable<T> (source, dt, LoadOption.PreserveChanges);
            return dt;
        }

        public static void CopyToDataTable<T> (this IEnumerable<T> source, DataTable table, LoadOption options)
            where T : DataRow
        {

            if (object.ReferenceEquals(typeof(T), typeof(System.Data.DataRow)))
            {

                foreach (System.Data.DataRow drRowToCopy in source)
                {
                    System.Data.DataRow drNewRow = table.NewRow();

                    for (int i = 0; i < drRowToCopy.ItemArray.Length; ++i)
                    {
                        drNewRow[i] = drRowToCopy[i];
                    } // Next i

                    table.Rows.Add(drNewRow);
                } // Next dr

            }
            else
                CopyToDataTable<T>(source, table, options, null);
        }

即使一行中的一列中只有一个值是 NULL,也会出现问题...

谁能建议我如何解决这个问题?
或者当一个或两个字段为 NULL 时,我如何才能从 IEnumerable 生成 DataTable 而不会出现异常?

现在我通过在比较中捕获异常来“修复”它。
但这只是 .NET 2.0 的修复,我可以在其中执行此操作,因为此类不存在。
我需要一个真正适用于 .NET 4.0 的修复程序。

public override int Compare (int first_index, int second_index)
        {
            int comparison = 0;
            try
            {
                comparison = comparer.Compare(keys[first_index], keys[second_index]);
            }
            catch (Exception ex)
            {
                //Console.WriteLine(ex.Message);
            }

            if (comparison == 0) {
                if (child_context != null)
                    return child_context.Compare (first_index, second_index);

                comparison = direction == SortDirection.Descending
                    ? second_index - first_index
                    : first_index - second_index;
            }

            return direction == SortDirection.Descending ? -comparison : comparison;
        }

PS:对于我的 Linq.Dynamic DataTable-OrderBy-Enhanced 版本,请参见此处:
http://pastebin.com/PuqtQhfa

最佳答案

好的,我得到了解决方案:

很明显,它与 System.DbNull 类型有关

首先,因为我的 DataTable 中只有字符串,所以我得到了错误消息:

Object must be of type "string"

但是字符串怎么可能不是字符串类型呢?当然只有当它是 DbNull 时。

如果你有两个相邻的 DbNull.Values,你当然会得到:

At least one object must implement IComparable

因为 DbNull 没有实现 IComparable。

毕竟,有趣的是,它只在排序列是一个具有 NULL 值的列时才会失败,但如果它是一个没有 NULL 值的列,它就可以完美地工作。

由于表本身包含所有空值,与顺序无关,因此 CopyToDataTable 有时不起作用是不合逻辑的,因为它每次都复制所有值,与顺序无关。

唯一合乎逻辑的结论是,在代码中调用 OrderBy 时不会执行它,只有在某些方法实际使用 OrderBy 生成的数据时才会执行。

快速谷歌搜索让我找到了这个 http://blogs.msdn.com/b/charlie/archive/2007/12/09/deferred-execution.aspx

只需要阅读前几行就可以知道问题所在:

This post covers one of the most important and frequently misunderstood LINQ features. Understanding deferred execution is a rite of passage that LINQ developers must undergo before they can hope to harness the full power of this technology.

所以我突然意识到我刚刚通过了通行权:)

显然,缺陷是 e.MoveNext() 在排序时触发比较,该比较在 DbNull 上失败,因为所说的 DbNull 没有实现 IComparable。

具有讽刺意味的是,数据表也可以使用 select 语句进行排序,这我最初并不知道(毕竟,我希望“order”方法被称为“order”,而不是“select”.. .) 所以我只是将 Linq.Dynamic 中的 OrderBy 更改为

public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values)
{
    if (source == null) throw new ArgumentNullException("source");
    if (ordering == null) throw new ArgumentNullException("ordering");

    if (object.ReferenceEquals(source.ElementType, typeof(System.Data.DataRow)))
    {
        using (DataTable dt = source.Cast<System.Data.DataRow>().CopyToDataTable())
        {
            return dt.Select("", ordering).AsQueryable();
        }

瞧,错误消失了。
而且由于单独使用 Select 过滤它比使用 Linq.Dynamic 更有效(它制作所有数据的大约 3 个副本),我决定完全放弃 Linq.Dynamic。
我仍在使用 Linq take and skip,但将来我肯定会更不愿意使用 Linq。

延迟执行是非常危险的,因为它会导致非常难以追踪的错误。
“boooom”所需要的只是错误位置的空值,或缺少接口(interface)(以及缺少检查或缺少泛型限制,如本例所示)...

关于c# - CopyToDataTable : Why exactly does it throw an error if one field is NULL, 以及如何修复它?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12050513/

相关文章:

c# - 免费安装程序是否可以轻松地将应用程序安装为 Windows 服务?

c# - 将表达式解析为参数

.net - Jetbrains 骑士 : Show "external code" in debugger call stack

c# - 我的 C# 应用程序中应该使用多少个 DataTable 对象?

c# - 匿名类、临时数据和匿名类的集合

C# Linq Char 数组 Except() - 奇怪的行为

sql - 对于 Entity Framework ,对于 "TOP 1"使用 .First() 还是 .Take(1) 更好?

c# - 如何使用 DataContractSerializer 反序列化具有未命名类型集合的 JSON

c# - 如何更改远程配置文件中的配置源值

java - 将日期时间戳转换为 RSS pubDate