简体   繁体   中英

NHibernate: intercept and modify query parameters?

tldr: Can I intercept query parameters when a select query is being constructed, and modify them? I can't find any event listener or interceptor that seems to be able to do this. In my case I'd like to inspect all query parameters before they are bound, looking for specific DateTime query parameters with Kind=Unspecified, and set their Kind to Local.

edit: solved with IUserType, see below


After a BIG version upgrade (3.4 to 5.3), I ran into a problem querying an entity by a DateTime property mapped as LocalDateTimeType. A simplified example of the entity mapping, in part, and a use case for querying by the DateTime property:

public class MyEntity {
    ...
    public virtual DateTime EntityDate { get; set; }
    ...
}
public class MyEntityMap : ClassMap<MyEntity> {
    public MyEntityMap() {
        ...
        Map(x => x.EntityDate).Column("entitydate").Not.Nullable().CustomType<LocalDateTimeType>();
        ...
    }
}
...
public IEnumerable<MyEntity> FindAllBeforeDate(DateTime date) {
    return session.QueryOver<MyEntity>().Where(x => x.EntityDate < date).List();
}

The codebase has lots of other use cases for querying on this property, implemented using QueryOver, Criteria queries, and HQL. In every case, post-upgrade these queries throw an exception:

LocalDateTime expect date kind Local but it is Unspecified\r\nParameter name: value
at NHibernate.Type.AbstractDateTimeType.Set(DbCommand st, Object value, Int32 index, ISessionImplementor session)
   at NHibernate.Param.NamedParameterSpecification.Bind(DbCommand command, IList`1 multiSqlQueryParametersList, Int32 singleSqlParametersOffset, IList`1 sqlQueryParametersList, QueryParameters queryParameters, ISessionImplementor session)
   at NHibernate.Param.NamedParameterSpecification.Bind(DbCommand command, IList`1 sqlQueryParametersList, QueryParameters queryParameters, ISessionImplementor session)
   at NHibernate.SqlCommand.SqlCommandImpl.Bind(DbCommand command, ISessionImplementor session)
   at NHibernate.Loader.Loader.PrepareQueryCommand(QueryParameters queryParameters, Boolean scroll, ISessionImplementor session)
   at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies, IResultTransformer forcedResultTransformer, QueryCacheResultBuilder queryCacheResultBuilder)
   at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies, IResultTransformer forcedResultTransformer, QueryCacheResultBuilder queryCacheResultBuilder)
   at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters, IResultTransformer forcedResultTransformer, QueryCacheResultBuilder queryCacheResultBuilder)

In other words, I'm trying to query by EntityDate with a parameter DateTime value that has DateTimeKind=Unspecified. nHibernate now enforces that the parameter value MUST have DateTimeKind=Local for any LocalDateTimeType properties; so the query builder won't bind my parameter value.

I could go through every single place in the application that queries on this property (there are many) and fix each one individually. But that sucks, in my opinion. How can I intercept or listen to an event that's positioned before the parameter binding process, look for instances of querying on "entitydate" with a bad parameter value, and fix the parameter value to have the proper DateTimeKind? I can't find any interceptor or event listener that seems to be able to do this.

Turns out I was looking at the wrong part of the API, and this was solved using a custom user type instead of event listener or interceptor. The custom type is an implementation of IUserType that wraps a DateTime and forcibly converts any parameter of a DbCommand mapped to the type, to have DateTimeKind.Local.

[Serializable]
public class LocalKindDateTimeType : IUserType
{
    public SqlType[] SqlTypes => new SqlType[]
    {
        new SqlType(DbType.DateTime)
    };

    public Type ReturnedType => typeof(DateTime);

    public bool IsMutable => false;

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

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

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

    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 NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner)
    {
        if (owner == null)
            return null;
        return DateTime.SpecifyKind((DateTime)NHibernateUtil.DateTime.NullSafeGet(rs, names, session), DateTimeKind.Local);
    }

    public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session)
    {
        if (value is DateTime date)
            NHibernateUtil.DateTime.NullSafeSet(cmd, DateTime.SpecifyKind(date, DateTimeKind.Local), index, session);
        else
            NHibernateUtil.DateTime.NullSafeSet(cmd, value, index, session);
    }

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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