"4" и "4" столкновение в первичном ключе, но не в файловой системе

Существует DataTable с первичным ключом для хранения информации о файлах. Бывают 2 файла, которые отличаются по именам символами "4" и "4" (0xff14, символ "Fullwidth Digit Four"). DataTable не может включать их как из-за неудачной уникальности. Однако в файловой системе Windows они, похоже, могут сосуществовать без каких-либо проблем.

Поведение, по-видимому, не зависит от настроек локали, я изменил "Region & Language-> Formats-> Format" с английского на японский, а также "язык для не-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: Локаль рассматривается как: enter image description here

Ответы

Ответ 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");