简体   繁体   English

Fluent NHibernate-从SQL转换为C#类型时如何进行附加处理?

[英]Fluent NHibernate - How to do Additional Processing when Converting from SQL to C# type?

I'm using Fluent NHibernate as my Data Access layer, and I need to do this every time a value in SQL is mapped to the DateTime type: 我使用Fluent NHibernate作为我的数据访问层,并且每次将SQL中的值映射到DateTime类型时都需要这样做:

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

In the code above, newDateTime represents the value that should be returned for all SQL to C# conversions, and oldDateTime represents what the default converters for NHibernate automatically convert to. 在上面的代码中,newDateTime表示所有SQL到C#转换应返回的值,而oldDateTime表示NHibernate的默认转换器自动转换为的值。

Besides the issue that Fluent NHibernate documentation is very bleak, I've tried searching around the internet for conventions that will let me do this, but IUserType is too heavy (and I haven't been able to find a comprehensive explanation of how implement the methods derived from IUserType), and IPropertyConvention seems to only offer ways to modify how C# is being converted to SQL (not the other way around, which is what I need in this scenario). 除了Fluent NHibernate文档非常暗淡的问题外,我还尝试在互联网上搜索可以使我做到这一点的约定,但是IUserType太繁琐了(而且我无法找到有关如何实现该规则的全面解释。从IUserType派生的方法)和IPropertyConvention似乎仅提供修改C#转换为SQL的方式的方法(没有其他方法,这是我在此方案中需要的方法)。

Can someone please point me in the right direction? 有人可以指出正确的方向吗? And/or provide some quality links to read up on conventions? 和/或提供一些高质量的链接以阅读约定? None of the wiki pages explain anything in detail so please refrain from linking those. 维基页面均未详细解释任何内容,因此请不要链接这些内容。 Thank you. 谢谢。

NHibernate (not only Fluent) supports setting, distinguishing how to handle the DateTime stored in DB (regardless of some DB Support for offset eg datetimeoffset (Transact-SQL) ) . NHibernate (不仅是Fluent)支持设置,区分如何处理存储在数据库中的DateTime (无论某些DB支持偏移量,例如datetimeoffset(Transact-SQL) See 5.2.2. 参见5.2.2。 Basic value types 基本值类型

GET from DB: 从数据库获取:

So, we can explicitly define, how to treat the value returned from a table column like this: 因此,我们可以显式定义如何处理从表列返回的值,如下所示:

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

So, once retrieved from DB, all DateTime properties will be automatically provided with correct Kind setting: 因此,一旦从数据库检索到,所有DateTime属性将自动提供正确的Kind设置:

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

SET

Let me provide some extract from Date/Time Support in NHibernate : 让我从NHibernate的日期/时间支持中提供一些摘录:

Notice that NHibernate did not perform any conversions or throw an exception when saving/loading a DateTime value with the wrong DateTimeKind. 请注意,当使用错误的DateTimeKind保存/加载DateTime值时,NHibernate不会执行任何转换或引发异常。 (It could be argued that NHibernate should throw an exception when asked to save a Local DateTime and the property is mapped as a UtcDateTime.) It is up to the developer to ensure that the proper kind of DateTime is in the appropriate field/property. (可以争论的是,当NHibernate被要求保存本地DateTime并且该属性被映射为UtcDateTime时,应该引发异常。)由开发人员负责确保适当类型的DateTime位于适当的字段/属性中。

Other words, the Entity DateTime , coming from a client (during the binding, deserializing etc) must be correctly set in the custom == our code. 换句话说,来自客户端(在绑定,反序列化等过程中)的Entity DateTime必须在自定义==我们的代码中正确设置。

An example, in Web API, during the conversion of the DateTime from a JSON. 在Web API中,从JSON转换DateTime期间的示例。 While the JSON is UTC, the DB is set to lcoal. 当JSON为UTC时,数据库设置为lcoal。 We can inject this converter and be sure, that: 我们可以注入此转换器,并确保:

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

And now we can be sure that: 现在我们可以确定:

  1. GET - our mapping .CustomType<LocalDateTimeType>() will correctly inform the application that data coming from DB are in Local zone .CustomType<LocalDateTimeType>()我们的映射.CustomType<LocalDateTimeType>()将正确通知应用程序来自DB的数据位于本地区域
  2. SET - the converter, will correctly set the DateTime values to Local zone SET-转换器,将把DateTime值正确设置为Local zone

I ended up implementing a new user type using IUserType. 我最终使用IUserType实现了新的用户类型。 In fact, I found a novel way of minimizing the work needed to implement new user type for immutable types, since only a few of the members need to really be implemented when extending an immutable type such as DateTime. 实际上,我发现了一种新颖的方法,可以最大限度地减少为不可变类型实现新用户类型所需的工作,因为在扩展不可变类型(如DateTime)时,仅需要真正实现少数成员。 Here's my code: 这是我的代码:

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

Basically, I have created (copied from somewhere else, sorry forgot source) a base class called BaseImmutableUserType<T> that essentially creates an IUserType based on an immutable type, but allows subclasses to extend the functionality of the NullSafeGet() and NullSafeSet() operations (which are essentially the Get and Set operations and alongwith SqlTypes, that's all I need to override in the subclass). 基本上,我已经创建了(从其他地方复制,很遗憾,从忘记的地方复制了)一个名为BaseImmutableUserType<T>的基类,该基类实质上是基于不可变类型创建IUserType的,但是允许子类扩展NullSafeGet()NullSafeSet()操作(本质上是Get和Set操作以及SqlType,这就是我需要在子类中重写的所有操作)。 I imagine I'll need to override different types more in the long run, so I decided to use a generalized solution for immutable types. 我想从长远来看,我将需要更多地覆盖不同的类型,因此我决定对不可变类型使用通用解决方案。 Another point to note is that I had to do this both both DateTime and DateTime? 还要注意的一点是,我必须同时执行DateTimeDateTime? . This is my Fluent configuration: 这是我的Fluent配置:

_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()
                                        ));

Please let me know if there are questions and I'll try to answer as quick as possible. 如果有问题,请告诉我,我将尽快回答。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM