Nhibernate方言,将小数从5位转换为2位的精度

Nhibernate dialect, precision for Casting decimal from 5 places to 2 places

我们在数据库中有一个 decimal(20,5) 类型的货币字段。 如何将 CAST 或 COnvert 添加到传出的 Nhibernate 标准?

   SELECT
        TOP (1000)  this_.DepositAccountId as DepositA1_71_0_,
        this_.BranchId as BranchId71_0_,
        this_.ConfigurationStatusId as Configu14_71_0_,
        this_.ConfiguredBy as Configu41_71_0_,
        this_.ConfiguredDate as Configu15_71_0_,
        this_.DepositAccountBalance as DepositA9_71_0_
    FROM
        dbo.DepositAccount this_ 
    WHERE
        Convert(Decimal(20,2), this_.DepositAccountBalance) = 1.01

目前它发送这样的 WHERE 子句

WHERE
           this_.DepositAccountBalance = 1.01

我需要添加一个 Convert 或 cast 或 round。

数据库中有一条 DepositAccountBalance = 1.0107 的记录。 因此,如果没有 Cast 或 Convert,就没有匹配项。

网站上有一些资源,人们在其中为生成的 .hbm.xml 添加了精度和比例。其中一些推荐使用 Nhibernate 方言和自定义 SQL 函数

谁能解释一下我需要其中的哪一个?我什么时候使用 Nhibernate Dialect。什么时候将 Precision 添加到 .hbm.xml。我需要做的就是在 where 子句中添加一个 Convert 或 round

所以这就是应该的工作(我大大简化了查询,但问题都是一样的):

IType decimalType = TypeFactory.Basic("decimal(20,2)");

IProjection castProjection = Projections.Cast(
    decimalType,
    Projections.Property<DepositAccount>(acct => acct.DepositAccountBalance));

var accounts = session.QueryOver<DepositAccount>()
    .Where(Restrictions.Eq(castProjection, 1.01))
    .List<DepositAccount>();

不幸的是,这会生成以下 SQL:

SELECT         
    this_.*
FROM         
    DepositAccount this_     
WHERE         
    cast( this_.DepositAccountBalance as DECIMAL(19,5)) = 1.01

嗯?我们刚刚指定我们想要一个类型 decimal(20,2)!发生什么事了?

看起来 CastProjection 完全忽略了您传递给它的类型的精度和小数位数。这是 CastProjection class:

中的相关代码
public override SqlString ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
{
    ISessionFactoryImplementor factory = criteriaQuery.Factory;
    SqlType[] sqlTypeCodes = type.SqlTypes(factory);
    if (sqlTypeCodes.Length != 1)
    {
        throw new QueryException("invalid Hibernate type for CastProjection");
    }

    // HERE: precision and scale are being ignored.
    string sqlType = factory.Dialect.GetCastTypeName(sqlTypeCodes[0]);
    int loc = position*GetHashCode();
    SqlString val = projection.ToSqlString(criteria, loc, criteriaQuery,enabledFilters);
    val = SqlStringHelper.RemoveAsAliasesFromSql(val);

    return new SqlString("cast( ", val, " as ", sqlType, ") as ", GetColumnAliases(position, criteria, criteriaQuery)[0]);
}

对于所有 decimal 类型,GetCastTypeName 只是 returns decimal(19,5),这似乎是一个错误。

有两种方法可以解决这个问题:

  1. 使用Projections.SqlFunction(推荐——不知道#2 的实际后果是什么)

    为此,我们只需要使用 Projections.SqlFunction 即可明确地为我们执行 cast

    var decimalType = TypeFactory.Basic("decimal(20,2)");
    
    var castProjection = Projections.SqlFunction(
        new SQLFunctionTemplate(decimalType, "cast(?1 as decimal(20,2))"),
            decimalType,
            Projections.Property<DepositAccount>(acct=> acct.DepositAccountBalance));
    
    var q = session.QueryOver<DepositAccount>()
        .Where(Restrictions.Eq(castProjection, 1.01))
        .List<DepositAccount>();
    

    这会生成预期的 SQL:

    SELECT        
        this_.* 
    FROM         
        DepositAccount this_     
    WHERE         
        cast(this_.DepositAccountBalance as decimal(20,2)) = 1.01
    
  2. 编写我们自己的 class 以正确进行转换。我们真的只需要更改一行代码就可以让它工作。否则它与 CastProjection class:

    完全相同
    public class PrecisionCast : SimpleProjection 
    {
        private readonly IType type;
        private readonly IProjection projection;
    
        public PrecisionCast(IType type, IProjection projection)
        {
            this.type = type;
            this.projection = projection;
        }
    
        public override bool IsAggregate
        {
            get { return false; }
        }
    
        public override SqlString ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
        {
            ISessionFactoryImplementor factory = criteriaQuery.Factory;
            SqlType[] sqlTypeCodes = type.SqlTypes(factory);
    
    
            if (sqlTypeCodes.Length != 1)
            {
                throw new QueryException("invalid Hibernate type for CastProjection");
            }       
    
            // Get the type name, preserving scale and precision
            string sqlType = factory.Dialect.GetTypeName(sqlTypeCodes[0]);
    
            int loc = position*GetHashCode();
            SqlString val = projection.ToSqlString(criteria, loc, criteriaQuery,enabledFilters);
            val = SqlStringHelper.RemoveAsAliasesFromSql(val);
    
            return new SqlString("cast( ", val, " as ", sqlType, ") as ", GetColumnAliases(position, criteria, criteriaQuery)[0]);
        }
    
        public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            return new IType[]{ type };
        }
    
        public override NHibernate.Engine.TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            return projection.GetTypedValues(criteria, criteriaQuery);
        }
    
        public override bool IsGrouped
        {
            get
            {
                return projection.IsGrouped;
            }
        }
    
        public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
        {
            return projection.ToGroupSqlString(criteria, criteriaQuery, enabledFilters);
        }
    }
    

    然后像这样使用它:

    var decimalType = TypeFactory.Basic("decimal(20,2)");
    
    var castProjection = new PrecisionCast(
        decimalType, Projections.Property<DepositAccount>(acct => acct.DepositAccountBalance));
    
    var accounts = session.QueryOver<DepositAccount>()
        .Where(Restrictions.Eq(castProjection, 1.01))
        .List<DepositAccount>();
    

    这似乎解决了 decimal 类型的问题,但我不知道对其他类型会有什么影响,因此无法保证此代码。

希望对您有所帮助。我会选择#1。