14

別の ORM から NHibernate を使用するためのアプリケーションの移植。

インメモリ SQLite データベースに対して単体テストを実行する機能を導入し始めました。これは、テストの最初の数バッチで機能しますが、問題が発生しました。私たちのアプリは実際には SQL 2008 サーバーと通信するため、現在、いくつかのモデルには DateTimeOffset プロパティがあります。テスト以外のアプリケーションで SQL 2008 との間でマッピングする場合、これはすべて正常に機能します。

SQLite テストフィクスチャからセッションを使用すると、DateTimeOffset がよりプラットフォームに依存しない DateTime として「自動的に」処理されるように、データベースまたはその他の機能を構成するメカニズムがありますか?

4

3 に答える 3

13

偶然にも、私は今日この問題に遭遇しました:)私はこのソリューションを完全にテストしていません.NHibernateは初めてですが、私が試した些細なケースではうまくいくようです.

最初に、DateTimeOffset から DateTime に変換する IUserType 実装を作成する必要があります。Ayende ブログにユーザー タイプを作成する方法の完全な例がありますが、目的に関連するメソッドの実装は次のとおりです。

public class NormalizedDateTimeUserType : IUserType
{
    private readonly TimeZoneInfo databaseTimeZone = TimeZoneInfo.Local;

    // Other standard interface  implementations omitted ...

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

    public SqlType[] SqlTypes
    {
        get { return new[] { new SqlType(DbType.DateTime) }; }
    }

    public object NullSafeGet(IDataReader dr, string[] names, object owner)
    {
        object r = dr[names[0]];
        if (r == DBNull.Value)
        {
            return null;
        }

        DateTime storedTime = (DateTime)r;
        return new DateTimeOffset(storedTime, this.databaseTimeZone.BaseUtcOffset);
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        if (value == null)
        {
            NHibernateUtil.DateTime.NullSafeSet(cmd, null, index);
        }
        else
        {
            DateTimeOffset dateTimeOffset = (DateTimeOffset)value;
            DateTime paramVal = dateTimeOffset.ToOffset(this.databaseTimeZone.BaseUtcOffset).DateTime;

            IDataParameter parameter = (IDataParameter)cmd.Parameters[index];
            parameter.Value = paramVal;
        }
    }
}

このdatabaseTimeZoneフィールドTimeZoneには、データベースに値を格納するために使用されるタイム ゾーンを表す が保持されます。すべてのDateTimeOffset値は、保存前にこのタイム ゾーンに変換されます。私の現在の実装では、ローカル タイム ゾーンにハードコードされていますが、いつでも ITimeZoneProvider インターフェイスを定義して、コンストラクターに挿入することができます。

すべてのクラス マップを変更せずにこのユーザー タイプを使用するために、Fluent NH で規約を作成しました。

public class NormalizedDateTimeUserTypeConvention : UserTypeConvention<NormalizedDateTimeUserType>
{
}

そして、この例のように、この規則をマッピングに適用しました (これnew NormalizedDateTimeUserTypeConvention()が重要な部分です)。

mappingConfiguration.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())
                .Conventions.Add(
                PrimaryKey.Name.Is(x => x.EntityType.Name + "Id"), 
                new NormalizedDateTimeUserTypeConvention(),
                ForeignKey.EndsWith("Id"));

私が言ったように、これは完全にテストされていないので、注意してください! しかし今は、1 行のコード (流暢なマッピング仕様) を変更するだけで、データベースで DateTime と DateTimeOffset を切り替えることができます。


編集

要求に応じて、Fluent NHibernate 構成:

SQL Server のセッション ファクトリを構築するには:

private static ISessionFactory CreateSessionFactory(string connectionString)
{
    return Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
            .Mappings(m => MappingHelper.SetupMappingConfiguration(m, false))
            .BuildSessionFactory();
}

SQLite の場合:

return Fluently.Configure()
            .Database(SQLiteConfiguration.Standard.InMemory)
            .Mappings(m => MappingHelper.SetupMappingConfiguration(m, true))
            .ExposeConfiguration(cfg => configuration = cfg)
            .BuildSessionFactory();

SetupMappingConfiguration の実装:

public static void SetupMappingConfiguration(MappingConfiguration mappingConfiguration, bool useNormalizedDates)
{
    mappingConfiguration.FluentMappings
        .AddFromAssembly(Assembly.GetExecutingAssembly())
        .Conventions.Add(
            PrimaryKey.Name.Is(x => x.EntityType.Name + "Id"), 
            ForeignKey.EndsWith("Id"));

    if (useNormalizedDates)
    {
        mappingConfiguration.FluentMappings.Conventions.Add(new NormalizedDateTimeUserTypeConvention());
    }
}
于 2010-02-21T12:44:29.403 に答える
5

元のタイムゾーンオフセットを追跡できる別の提案:

public class DateTimeOffsetUserType : ICompositeUserType
{
    public string[] PropertyNames
    {
        get { return new[] { "LocalTicks", "Offset" }; }
    }

    public IType[] PropertyTypes
    {
        get { return new[] { NHibernateUtil.Ticks, NHibernateUtil.TimeSpan }; }
    }

    public object GetPropertyValue(object component, int property)
    {
        var dto = (DateTimeOffset)component;

        switch (property)
        {
            case 0:
                return dto.UtcTicks;
            case 1:
                return dto.Offset;
            default:
                throw new NotImplementedException();
        }
    }

    public void SetPropertyValue(object component, int property, object value)
    {
        throw new NotImplementedException();
    }

    public Type ReturnedClass
    {
        get { return typeof(DateTimeOffset); }
    }

    public new bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, null) && ReferenceEquals(y, null))
            return true;

        if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
            return false;

        return x.Equals(y);
    }

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

    public object NullSafeGet(IDataReader dr, string[] names, ISessionImplementor session, object owner)
    {
        if (dr.IsDBNull(dr.GetOrdinal(names[0])))
        {
            return null;
        }

        var dateTime = (DateTime)NHibernateUtil.Ticks.NullSafeGet(dr, names[0], session, owner);
        var offset = (TimeSpan)NHibernateUtil.TimeSpan.NullSafeGet(dr, names[1], session, owner);

        return new DateTimeOffset(dateTime, offset);
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index, ISessionImplementor session)
    {
        object utcTicks = null;
        object offset = null;

        if (value != null)
        {
            utcTicks = ((DateTimeOffset)value).DateTime;
            offset = ((DateTimeOffset)value).Offset;
        }

        NHibernateUtil.Ticks.NullSafeSet(cmd, utcTicks, index++, session);
        NHibernateUtil.TimeSpan.NullSafeSet(cmd, offset, index, session);
    }

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

    public bool IsMutable
    {
        get { return false; }
    }

    public object Disassemble(object value, ISessionImplementor session)
    {
        return value;
    }

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

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

DateTimeOffset ICompositeUserType から流暢な NNibernate の規則は次のようになります。

public class DateTimeOffsetTypeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
    public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
    {
        criteria.Expect(x => x.Type == typeof(DateTimeOffset));
    }

    public void Apply(IPropertyInstance instance)
    {
        instance.CustomType<DateTimeOffsetUserType>();
    }
}
于 2011-01-19T20:46:34.037 に答える
3

担当者が不足しているため、これを受け入れられた回答にコメントとして追加することはできませんが、受け入れられた回答にソリューションを実装しているときに見つけた追加情報を追加したかったのです。私も、スキーマのエクスポートを呼び出すときに方言が DateTimeOffset をサポートしていないというエラーを受け取りました。log4net ロギングのサポートを追加した後、プロパティのタイプが DateTimeOffset? であることがわかりました。大会では取り扱われませんでした。つまり、規則はnull 許容のDateTimeOffset プロパティに適用されていませんでした。

これを解決するために、NormalizedDateTimeUserType から派生し、ReturnedType プロパティをオーバーライドするクラスを作成しました (オリジナルを仮想としてマークする必要がありました)。次に、派生クラス用に 2 番目の UserTypeConvention を作成し、最終的に 2 番目の規則を構成に追加しました。

于 2011-01-24T18:17:52.637 に答える