C#标识符中的非ASCII字符被NHibernate引用。

6

我尝试在C#标识符中输入非ASCII字符,程序编译和运行都很好(至少乍一看是这样)。更准确地说,我在枚举类型中使用克罗地亚变音字符(čćđšž)。我真的需要使用这些特殊字符,因为我将字符串枚举映射为NHibernate中的值对象。如果在向用户显示外观时必须避免使用这些标准字符,那将非常丑陋。在大规模使用枚举之前,我真的需要知道是否存在任何含义(潜在问题)与此类编程技术有关(特别是在NHibernate方面)。或者,如果您有更好的处理方式,请告诉我。

此外,使用于重构、SVN等工具是否存在任何问题?


你能使用HTML字符吗?这是一个Web应用程序吗? - hunter
1
如果在枚举名称中加入“数据”,会听起来有些奇怪。不确定NHibernate是如何工作的,但这确实听起来不太对。 (还要考虑ASCII /英语通常是最低公共分母代码格式)。关于国际化的语言支持的有趣问题,值得探讨(+1)-希望能看到规范被提出。 - user166390
@hunter 有趣的问题。这可能是个问题。我会尝试使用MVC来解决它。 - zszep
@Željko,看看我做的这个小工具,它可以输出一些HTML字符。http://hunterconcepts.com/tools/developer/html 你可以点击它们,它会显示相应的&#... HTML等价字符。 - hunter
4个回答

6
C#语言使用Unicode编码,这意味着你的特殊字符不会成为问题。然而,我喜欢用英文保留我的代码,不使用特殊字符。我还没有找到一个正当理由来证明使用特定于文化的命名是必要的。

根据C#语言规范:

C#程序由一个或多个源文件组成,正式称为编译单元(§9.1)。源文件是Unicode字符的有序序列。源文件通常与文件系统中的文件一一对应,但不需要强制执行此对应关系。为了实现最大的可移植性,建议使用UTF-8编码对文件进行编码。

第2.1节(Microsoft C# Language Specification)


+1...现在快点,从规范!中选择一个章节号和摘录。 :) - user166390
我也是,但正如我所说,由于枚举类的缘故,我被迫这么做。 - zszep
@Željko:你可以使用其他方式将枚举映射到值,不一定要使用枚举作为值。 - Jeff Mercado
@Jeff M 有什么提示如何做到这一点吗?我在这个主题上进行了相当多的谷歌搜索,所有的文章都建议使用枚举来作为值对象。 - zszep
你可以创建一个属性来保存值。参考这篇文章:http://blog.waynehartman.com/articles/84.aspx - DEHAAS
显示剩余2条评论

3
我创建了这个自定义的用户类型,以便安全地将枚举转换为int类型,以供NHibernate使用。您应该更改函数以使用字符串而不是整数。然后,您可以更改“NullSafeGet”方法,将数据库值转换为枚举值。这样做,您可以在数据库中使用普通字符表示枚举值,并使用易于理解的名称。 编辑:我已经自己进行了更改。您可以使用此类作为自定义用户类型。您应该实现“SpecialConversion”方法,以便从您的特殊字符串转换为枚举类型,反之亦然。
public class EnumToSpecialStringType<TEnum> : IUserType
{
    #region IUserType Members

    /// <summary>
    /// Reconstruct an object from the cacheable representation. At the very least this
    /// method should perform a deep copy if the type is mutable. (optional operation)
    /// </summary>
    /// <param name="cached">the object to be cached</param>
    /// <param name="owner">the owner of the cached object</param>
    /// <returns>a reconstructed object from the cachable representation</returns>
    public object Assemble(object cached, object owner)
    {
        return DeepCopy(cached);
    }

    /// <summary>
    /// Return a deep copy of the persistent state, stopping at entities and at collections.
    /// </summary>
    /// <param name="value">generally a collection element or entity field</param>
    /// <returns>a copy</returns>
    public object DeepCopy(object value)
    {
        return value;
    }

    /// <summary>
    /// Transform the object into its cacheable representation. At the very least this
    /// method should perform a deep copy if the type is mutable. That may not be enough
    /// for some implementations, however; for example, associations must be cached as
    /// identifier values. (optional operation)
    /// </summary>
    /// <param name="value">the object to be cached</param>
    /// <returns>a cacheable representation of the object</returns>
    public object Disassemble(object value)
    {
        return DeepCopy(value);
    }

    /// <summary>
    /// Compare two instances of the class mapped by this type for persistent "equality"
    /// ie. equality of persistent state
    /// </summary>
    /// <param name="x"/>
    /// <param name="y"/>
    /// <returns/>
    public new bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        return x.Equals(y);
    }

    /// <summary>
    /// Get a hashcode for the instance, consistent with persistence "equality"
    /// </summary>
    public int GetHashCode(object x)
    {
        return x.GetHashCode();
    }

    /// <summary>
    /// Are objects of this type mutable?
    /// </summary>
    public bool IsMutable
    {
        get { return false; }
    }

    /// <summary>
    /// Retrieve an instance of the mapped class from a ADO.NET resultset.
    /// Implementors should handle possibility of null values.
    /// </summary>
    /// <param name="rs">a IDataReader</param>
    /// <param name="names">column names</param>
    /// <param name="owner">the containing entity</param>
    /// <returns/>
    /// <exception cref="HibernateException">HibernateException</exception>
    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        var value = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;

        // Put you special conversion here (string -> enum)
        var converted = SpecialConversion(value);

        return converted;
    }

    /// <summary>
    /// Write an instance of the mapped class to a prepared statement.
    /// Implementors should handle possibility of null values.
    /// A multi-column type should be written to parameters starting from index.
    /// </summary>
    /// <param name="cmd">a IDbCommand</param>
    /// <param name="value">the object to write</param>
    /// <param name="index">command parameter index</param>
    /// <exception cref="HibernateException">HibernateException</exception>
    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        var parameter = (IDataParameter)cmd.Parameters[index];

        // Do conversion from your enum to database string value
        var converted = SpecialConversion(value);

        parameter.Value = converted;
    }

    /// <summary>
    /// During merge, replace the existing (<paramref name="target"/>) value in the entity
    /// we are merging to with a new (<paramref name="original"/>) value from the detached
    /// entity we are merging. For immutable objects, or null values, it is safe to simply
    /// return the first parameter. For mutable objects, it is safe to return a copy of the
    /// first parameter. For objects with component values, it might make sense to
    /// recursively replace component values.
    /// </summary>
    /// <param name="original">the value from the detached entity being merged</param>
    /// <param name="target">the value in the managed entity</param>
    /// <param name="owner">the managed entity</param>
    /// <returns>the value to be merged</returns>
    public object Replace(object original, object target, object owner)
    {
        return original;
    }

    /// <summary>
    /// The type returned by <c>NullSafeGet()</c>
    /// </summary>
    public Type ReturnedType
    {
        get
        {
            return typeof(TEnum);
        }
    }

    /// <summary>
    /// The SQL types for the columns mapped by this type. 
    /// </summary>
    public SqlType[] SqlTypes
    {
        get
        {
            return new[] { new SqlType(DbType.String) };
        }
    }

    #endregion
}

我使用流畅的NHibernate,映射此自定义用户类型的方式如下:

Map(x => x.PropertyName, "ColumnName").CustomType<EnumToSpecialStringType<EnumType>>();
编辑:以下是有关如何使用xml映射文件来映射用户类型的帖子:

nHibernate映射到自定义类型


有没有想过如何在没有使用Fluent NHibernate的情况下进行映射? - zszep
@Željko Szep 我从未使用过 XML 映射文件。我更新了我的回答并给出了另一个 StackOverflow 帖子的链接。我希望另一个帖子能够帮到您! - Pierre-Luc Champigny

1

我这里只是写一些示例来展示如何进行映射。

考虑你的枚举,其中所有值都包含非ASCII字符:

public enum MyEnum
{
    NonAsciiName1,
    NonAsciiName2,
    NonAsciiName3,
}

你可以将其更改为这样,使得所有值都具有ASCII字符:
public enum MyEnum
{
    AsciiName1,
    AsciiName2,
    AsciiName3,
}

public static class MyEnumExtensions
{
    static readonly Dictionary<MyEnum, string> map = new Dictionary<MyEnum, string>
    {
        { MyEnum.AsciiName1, "NonAsciiName1" },
        { MyEnum.AsciiName2, "NonAsciiName2" },
        { MyEnum.AsciiName3, "NonAsciiName3" },
    };

    public static string GetValue(this MyEnum key)
    {
        return map[key];
    }
}

那么你的代码需要进行小改动才能使用这个功能:

// you probably had something like:
var myEnumValue = MyEnum.NonAsciiName1;
var value = myEnumValue.ToString();

// now becomes:
var myEnumValue = MyEnum.AsciiName1;
var value = myEnumValue.GetValue();

或者像DEHAAS建议的那样,使用类似的属性。但是这样你就必须使用反射来获取值,这会有一定的性能损失。
using System.ComponentModel;

public enum MyEnum
{
    [DescriptionAttribute("NonAsciiName1")] AsciiName1,
    [DescriptionAttribute("NonAsciiName2")] AsciiName2,
    [DescriptionAttribute("NonAsciiName3")] AsciiName3,
}

public static class MyEnumExtensions
{
    public static string GetValue(this MyEnum key)
    {
        return typeof(MyEnum).GetField(key.ToString())
                             .GetCustomAttributes(typeof(DescriptionAttribute), false)
                             .Cast<DescriptionAttribute>()
                             .Single()
                             .Description;
    }
}

这看起来很有前途。明天一早我会试试看。唯一让我担心的是Ascii名称(非克罗地亚语)将存储在数据库中(例如,我们有“muški”(男性)和“ženski”(女性)作为性别。我必须将它们保存为“muski”和“zenski”到数据库中。 - zszep
@Željko:我对Nhibernate不是很了解,但你可能不必走那么远。 如果它与LINQ to SQL类似,您可以更改映射以将属性映射到列,使得ASCII属性映射到非ASCII列。 我想应该有一种方法来做到这一点。 Tristan的评论表明这是完全可能的。 - Jeff Mercado
这更像是Entity Framework。我同意你的看法。也许枚举并不是处理这种情况的最佳方式(尽管几乎每个NHibernate示例都是这样处理的)。我会尝试另一种方法。 - zszep

1

是的,我认为这是一个缺陷。

向用户显示查找结果并保留数据库中可能(唯一)的项列表最好分开保存,特别是如果您的应用程序未来将支持其他语言。

这不是很面向对象,您可以在代码中拥有一个类,该类是您的枚举和相应语言值之间的直接映射,例如在 nhibernate 映射中:

<class name="DropDown" table="DropDown_Language">
    <!-- map onto the ENUMeration -->
    <id name="UniqueID" column="Id" type="Int32" >
        <generator class="identity" />
    </id>
    <property name="DropDownType" column="DropDownTypeId" type="DropDownType, Domain"/>
    <property name="Language" column="LanguageId" type="LanguageReferenceType,  "/>
    <property name="DropDownTypeDescription" column="DropDownTypeDescription" type="string"/>
</class>

在DropDownTypeDescription列中,您将放置下拉列表的值。
希望我理解了您的问题 :-)

是的,但我不想映射完整的实体。我想要映射的只是简单的值对象,其中包含2或3个可能的值(例如性别、服装尺码:S M L XL)。 - zszep
我明白了。只是想引起你的注意,当你单独映射克罗地亚枚举时,可能会在未来的扩展问题上遇到困难,然后你必须用其他语言在应用程序中进行操作。(在我们的代码库中,我们已经映射了性别)。 - Hace
我同意你的看法,使用我的枚举方法进行国际化将会很麻烦。如果你查看Jeff M的回答,这可能可以解决我的问题(使用ASCII枚举,并为演示目的提供本地化字符串,可以从资源文件中读取)。 - zszep

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接