.net NHibernate可为空的列Map到非空属性

n6lpvg4x  于 2023-02-20  发布在  .NET
关注(0)|答案(1)|浏览(109)

当有一个可以为空的数据库字段,开发人员忘记在相应的. Net实体中声明它可以为空时,我遇到了错误的NHibernateMap问题。
表格:

CREATE TABLE myTable
(
   ID int NOT NULL,
   Total int NOT NULL,
   Discount int NULL --Nullable field
)

INSERT INTO myTable VALUES (1, 10, NULL)

C#实体:

public class MyTable{
   public int ID { get; set; }
   public int Total { get; set; }
   public int Discount { get; set; } //not declared as nullable by mistake
}

NHibernateMap:

public class MyTableMap : ClassMap<MyTable>
{
    public MyTableMap()
    {
        Table("dbo.myTable");
        Id(x => x.ID).GeneratedBy.Assigned().Column("ID");
        Map(x => x.Total).Column("Total");
        Map(x => x.Discount).Column("Discount"); //setting mapping to .Nullable() doesn't change the behaviour
    }
}

当我尝试加载实体时:

session.Get<MyTable>(1);

我本以为会得到一个异常,因为Discount字段为空,但NHibernate却以默认值0静默加载实体,然后在第一次会话时更新数据库表。Flush(),即使我没有更改实体的任何其他值。对于datetime字段,情况更糟,因为. Net DateTime的默认值是'01/01/0001',而我得到了异常:

The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value

其他人也遇到过同样的问题吗?SessionFactory中有没有配置强制NHibernate在NULL列Map到非空. Net属性时抛出异常?通过检查每个属性和列的每个Map很难解决这个问题,特别是当你在处理别人的代码时。

emeijp43

emeijp431#

按照注解中的建议,我为基元类型编写了一些约定,当数据库值为NULL且属性未声明为可空时,基元类型会在NullSafeGet方法上抛出异常。
例如,以下是应用于Int属性的自定义类型:

public class IntNotNullableType : Int32Type
{
    public override object NullSafeGet(DbDataReader rs, string name, ISessionImplementor session)
    {
        //Check if value returned by database is null, then throw an exception. 
        //Unfortunately column name is something like col_2_0_, but you should be able to see the full query in exception log, so you can find the wrong mapping
        if (rs.IsDBNull(name))
            throw new NoNullAllowedException("Column " + name + " returned NULL value for not nullable property");
        return base.NullSafeGet(rs, name, session);
    }
}

公约:

public class IntNotNullableTypeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
    public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
    {
        criteria.Expect(x => x.Property.PropertyType == typeof(int) && Nullable.GetUnderlyingType(x.Property.PropertyType) == null); //apply to all int properties NOT declared as nullable (int? or Nullable<int>)
    }

    public void Apply(IPropertyInstance instance)
    {
        instance.CustomType(typeof(IntNotNullableType));
    }
}

最后在SessionFactory中添加约定:

public static class SessionFactoryBuilder
{
    public static ISessionFactory Build(string connectionString)
    {
        return Fluently
            .Configure()
            .Database(() =>
            {
                return MsSqlConfiguration
                        .MsSql2012
                        .ConnectionString(connectionString);
            })
            .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())
            .Conventions.Add<IntNotNullableTypeConvention>() //add the convention
           )
            .BuildSessionFactory();
    }
}

您可以对所有其他基元类型(如DateTime、bool、double等)执行相同的操作。只需创建一个新的类型和约定,从正确的类型继承。
日期时间示例:

public class DateTimeNotNullableType : DateTimeType
{
    public override object NullSafeGet(DbDataReader rs, string name, ISessionImplementor session)
    {
        if (rs.IsDBNull(name))
            throw new NoNullAllowedException("Column " + name + " returned NULL value for not nullable property");
        return base.NullSafeGet(rs, name, session);
    }
}

public class DateTimeNotNullableTypeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
    public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
    {
        criteria.Expect(x => x.Property.PropertyType == typeof(DateTime) && Nullable.GetUnderlyingType(x.Property.PropertyType) == null);
    }

    public void Apply(IPropertyInstance instance)
    {
        instance.CustomType(typeof(DateTimeNotNullableType));
    }
}

public static ISessionFactory Build(string connectionString)
{
    return Fluently
        .Configure()
        .Database(() =>
        {
            return MsSqlConfiguration
                        .MsSql2012
                        .ConnectionString(connectionString);
        })
        .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())
        .Conventions.Add<IntNotNullableTypeConvention>() //add the convention
        .Conventions.Add<DateTimeNotNullableTypeConvention>()
        )
        .BuildSessionFactory();
 }

相关问题