2

我使用 Fluent NHibernate 作为我的数据访问层,每次将 SQL 中的值映射到 DateTime 类型时,我都需要这样做:

var newDateTime = DateTime.SpecifyKind(oldDateTime, DateTimeKind.Local);

在上面的代码中,newDateTime 表示所有 SQL 到 C# 转换应返回的值,oldDateTime 表示 NHibernate 的默认转换器自动转换为的值。

除了 Fluent NHibernate 文档非常黯淡的问题外,我还尝试在互联网上搜索可以让我这样做的约定,但 IUserType 太重(而且我无法找到关于如何实现的全面解释从 IUserType 派生的方法),而 IPropertyConvention 似乎只提供了修改 C# 如何转换为 SQL 的方法(而不是相反,这是我在这种情况下需要的)。

有人可以指出我正确的方向吗?和/或提供一些高质量的链接来阅读公约?wiki 页面都没有详细解释任何内容,因此请不要链接这些页面。谢谢你。

4

2 回答 2

4

NHibernate (不仅是 Fluent)支持设置,区分如何处理存储在 DB 中的 DateTime (不管某些 DB 支持偏移量,例如datetimeoffset (Transact-SQL)。见5.2.2。基本值类型

从数据库获取:

因此,我们可以显式定义,如何处理从表列返回的值,如下所示:

Map(x => x.ExpiryDate).CustomType<UtcDateTimeType>(); // UTC
Map(x => x.MaturityDate).CustomType<LocalDateTimeType>(); // local

因此,一旦从 DB 中检索到,所有 DateTime 属性都将自动提供正确的Kind设置:

Assert.IsTrue(entity.ExpiryDate.Kind == DateTimeKind.Utc);
Assert.IsTrue(entity.MaturityDate.Kind == DateTimeKind.Local);

让我提供一些来自NHibernate 中的日期/时间支持的摘录:

请注意,NHibernate 在保存/加载具有错误 DateTimeKind 的 DateTime 值时没有执行任何转换或引发异常。(可以说 NHibernate 在被要求保存本地 DateTime 并且属性映射为 UtcDateTime 时应该抛出异常。)由开发人员确保正确类型的 DateTime 在适当的字段/属性中。

换句话说,来自客户端的Entity (在绑定、反序列化等期间)必须在自定义 == 我们的代码中正确设置。 DateTime

一个示例,在 Web API 中,在从 JSON 转换 DateTime 期间。虽然 JSON 是 UTC,但数据库设置为 lcoal。我们可以注入这个转换器并确保:

class DateTimeConverter : IsoDateTimeConverter
{
    public DateTimeConverter()
    {
        DateTimeStyles = DateTimeStyles.AdjustToUniversal;
    }

    public override object ReadJson(JsonReader reader, Type objectType
           , object existingValue, JsonSerializer serializer)
    {
        var result = base.ReadJson(reader, objectType, existingValue, serializer);
        var dateTime = result as DateTime?;
        if (dateTime.Is() && dateTime.Value.Kind == DateTimeKind.Utc)
        {
            return dateTime.Value.ToLocalTime();
        }
        return result;
    }

现在我们可以确定:

  1. GET - 我们的映射.CustomType<LocalDateTimeType>()将正确通知应用程序来自数据库的数据在本地区域
  2. SET - 转换器,会将 DateTime 值正确设置为本地区域
于 2013-09-04T03:09:39.060 回答
0

我最终使用 IUserType 实现了一个新的用户类型。事实上,我发现了一种新颖的方法,可以最大限度地减少为不可变类型实现新用户类型所需的工作,因为在扩展不可变类型(如 DateTime)时,只有少数成员需要真正实现。这是我的代码:

[Serializable]
public class DateTimeKindLocalType : BaseImmutableUserType<DateTime>
{
    public override object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        //This is the line that I needed
        return DateTime.SpecifyKind((DateTime)NHibernateUtil.DateTime2.NullSafeGet(rs, names), DateTimeKind.Local);
    }

    public override void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        NHibernateUtil.DateTime2.NullSafeSet(cmd, value, index);
    }

    public override SqlType[] SqlTypes
    {
        get { return new[] {NHibernateUtil.DateTime2.SqlType}; }
    }
}

[Serializable]
public class DateTimeKindLocalTypeConvention
    : UserTypeConvention<DateTimeKindLocalType>
{
}

[Serializable]
public class DateTimeKindLocalNullableType : BaseImmutableUserType<DateTime?>
{
    public override object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        if (owner == null)
            return null;
        return DateTime.SpecifyKind((DateTime)NHibernateUtil.DateTime2.NullSafeGet(rs, names), DateTimeKind.Local);
    }

    public override void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        NHibernateUtil.DateTime2.NullSafeSet(cmd, value, index);
    }

    public override SqlType[] SqlTypes
    {
        get { return new[] { NHibernateUtil.DateTime2.SqlType }; }
    }
}

[Serializable]
public class DateTimeKindLocalNullableTypeConvention
    : UserTypeConvention<DateTimeKindLocalNullableType>
{
}


[Serializable]
public abstract class BaseImmutableUserType<T> : IUserType
{
    public abstract object NullSafeGet(IDataReader rs, string[] names, object owner);
    public abstract void NullSafeSet(IDbCommand cmd, object value, int index);
    public abstract SqlType[] SqlTypes { get; }

    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);
    }

    public int GetHashCode(object x)
    {
        return x.GetHashCode();
    }

    public object DeepCopy(object value)
    {
        return value;
    }

    public object Replace(object original, object target, object owner)
    {
        return original;
    }

    public object Assemble(object cached, object owner)
    {
        return DeepCopy(cached);
    }

    public object Disassemble(object value)
    {
        return DeepCopy(value);
    }

    public Type ReturnedType
    {
        get { return typeof(T); }
    }

    public bool IsMutable
    {
        get { return false; }
    }
}

基本上,我已经创建(从其他地方复制,抱歉忘记了源代码)一个名为的基类BaseImmutableUserType<T>,它基本上IUserType基于不可变类型创建一个,但允许子类扩展NullSafeGet()andNullSafeSet()操作的功能(本质上是 Get 和 Set 操作和连同 SqlTypes,这就是我需要在子类中覆盖的所有内容)。我想从长远来看,我需要更多地覆盖不同的类型,所以我决定对不可变类型使用通用解决方案。另一点需要注意的是,我必须同时DateTime执行DateTime?. 这是我的流利配置:

_fnhConfig = Fluently.Configure().Database(
                    MsSqlConfiguration.MsSql2008.ConnectionString(ConnectionString)                         
                    ).Mappings(m => m.FluentMappings.AddFromAssemblyOf<DataAccess.NHMG.Fluent.Mapping.DBBufferMap>()
                                        .Conventions.Add(
                                            DefaultLazy.Never()
                                            ,DefaultCascade.None()
                                    ===>    ,new DateTimeKindLocalTypeConvention()
                                    ===>    ,new DateTimeKindLocalNullableTypeConvention()
                                        ));

如果有问题请告诉我,我会尽快回答。

于 2013-09-05T13:56:38.033 回答