【问题标题】:'4' and '4' clash in primary key but not in filesystem'4' 和 '4' 在主键中冲突,但在文件系统中没有冲突
【发布时间】:2018-10-26 13:03:01
【问题描述】:

有一个带有主键的 DataTable 来存储有关文件的信息。碰巧有两个文件名称不同,符号为“4”和“4”(0xff14,“全角四位”符号)。由于唯一性失败,DataTable 未能将它们都包括在内。但是,在 Windows 文件系统中,它们似乎可以毫无问题地共存。

该行为似乎不依赖于区域设置,我将“区域和语言->格式->格式”从英语更改为日语,“非 unicode 程序的语言”也发生了变化。语言环境打印为“jp-JP”、“en-GB”。总是一样的结果。

问题:

  1. 有什么方法可以减少干扰?我可以切换到使用容器而不是 System.Data.* 但我想避免它。是否可以为列定义自定义比较器或更好地检查唯一性?启用区分大小写(这将解决这个问题)会导致其他问题。
  2. 是否有可能某些全局设置可以在不重建软件的情况下修复它?

失败的演示程序:

using System;
using System.Data;

namespace DataTableUniqueness
{
    class Program
    {
        static void Main(string[] args)
        {
            var changes = new DataTable("Rows");

            var column = new DataColumn { DataType = Type.GetType("System.String"), ColumnName = "File" };
            changes.Columns.Add(column);
            var primKey = new DataColumn[1];
            primKey[0] = column;
            changes.PrimaryKey = primKey;

            changes.Rows.Add("4.txt");
            try
            {
                changes.Rows.Add("4.txt"); // throws the exception
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: {0}", e);
            }
        }
    }
}

例外

Exception: System.Data.ConstraintException: Column 'File' is constrained to be unique.  Value '4.txt' is already present.
   at System.Data.UniqueConstraint.CheckConstraint(DataRow row, DataRowAction action)
   at System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent)
   at System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Boolean suppressEnsurePropertyChanged, Int32 position, Boolean fireEvent, Exception& deferredException)
   at System.Data.DataTable.InsertRow(DataRow row, Int64 proposedID, Int32 pos, Boolean fireEvent)
   at System.Data.DataRowCollection.Add(Object[] values)

PS:语言环境被视为:

【问题讨论】:

  • 这是一个有趣的行为,也许 change.Rows.Add("someFile") 将 unicode 字符解析成别的东西?
  • @mjwills,没有底层数据库,只是在内存数据中,如示例中
  • 这根本不能回答您的问题,但作为一种解决方法(如果您没有找到解决方案),您可以选择通过唯一 ID 引用您的文件,而不是使用 msdn.microsoft.com/en-us/library/aa364952(VS.85).aspx即让它们以您或您的用户希望选择的任何时髦文件名存在于磁盘上,但将它们添加到数据表中,然后通过 ID 窗口已经为它们查找它们。
  • 也许额外字节中的值的 ASCII 表示是不可打印的字符,因此是空格。在 DataTable 中进行比较时,值解析为“4”?
  • 显然是因为"4".Normalize(NormalizationForm.FormKC)"4".Normalize(NormalizationForm.FormKD) 都等于"4"。现在的问题是如何禁用它...

标签: c# windows unicode system.data


【解决方案1】:

通过使用DataType = typeof(object),您“禁用”了字符串规范化。字符串相等性仍然用于比较。不知道有没有其他副作用。

更复杂的解决方案:为string 类实现一个“包装器”:

public class MyString : IEquatable<MyString>, IComparable, IComparable<MyString>
{
    public static readonly StringComparer Comparer = StringComparer.InvariantCultureIgnoreCase;
    public readonly string Value;

    public MyString(string value)
    {
        Value = value;
    }

    public static implicit operator MyString(string value)
    {
        return new MyString(value);
    }

    public static implicit operator string(MyString value)
    {
        return value != null ? value.Value : null;
    }

    public override int GetHashCode()
    {
        return Comparer.GetHashCode(Value);
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is MyString))
        {
            return false;
        }

        return Comparer.Equals(Value, ((MyString)obj).Value);
    }

    public override string ToString()
    {
        return Value != null ? Value.ToString() : null;
    }

    public bool Equals(MyString other)
    {
        if (other == null)
        {
            return false;
        }

        return Comparer.Equals(Value, other.Value);
    }

    public int CompareTo(object obj)
    {
        if (obj == null)
        {
            return 1;
        }

        return CompareTo((MyString)obj);
    }

    public int CompareTo(MyString other)
    {
        if (other == null)
        {
            return 1;
        }

        return Comparer.Compare(Value, other.Value);
    }
}

然后:

var changes = new DataTable("Rows");

var column = new DataColumn { DataType = typeof(MyString), ColumnName = "File" };
changes.Columns.Add(column);
var primKey = new DataColumn[1];
primKey[0] = column;
changes.PrimaryKey = primKey;

changes.Rows.Add((MyString)"a");
changes.Rows.Add((MyString)"4.txt");
try
{
    changes.Rows.Add((MyString)"4.txt"); // throws the exception
}
catch (Exception e)
{
    Console.WriteLine("Exception: {0}", e);
}

var row = changes.Rows.Find((MyString)"A");

【讨论】:

  • 这似乎也可以区分大小写。但它看起来像是一种尝试更多的方式。
  • @max630 还有第二种可能性:在string 类周围实现一个“包装器”(我已经给出了一个示例)。请注意,NTFS 的排序规则(不区分大小写的名称比较)如何工作并不是很清楚(我找不到文档)。据我记得,NTFS 文件系统保存了它在文件系统中使用的排序规则(因此取决于您格式化高清的“世界上的哪个位置”、文件的排序以及哪些文件是唯一的可能会有所不同)。但我不知道如何找回它。这对于四个土耳其语 I (I ı İ i) 尤为重要
  • 在土耳其语中,大写的 i 是 İ(带点)
  • 我不是 100% 确定,但是对于 .net 中的字符串有一个特殊的比较,我希望与文件系统中的字符串相同地决定相等性。感谢您提供详细的示例,我会尝试一下。
  • @xanatos 我的经验是 NTFS 没有规范化,这符合 OP 描述的行为。不过,您对整理和案例折叠是正确的。它确实(永远)记住了格式化卷时有效的系统区域设置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-14
  • 1970-01-01
相关资源
最近更新 更多