-
Notifications
You must be signed in to change notification settings - Fork 931
Description
Hey,
First, I'd like to say I'm very impressed by the effort you guys put to make NHibernate better than ever (.NET Standard 2.0 is just awesome).
I'm currently writing NHibernate-based stores for my OpenIddict project and so far, the experience has been extremely pleasant. I'm only blocked by the fact the default EqualsGenerator
doesn't support the IEquatable<T>
interface, that I use in my NHibernate LINQ queries.
Consider the following model and mapping (a simplified copy of what's used in ASP.NET Core Identity 1.x/2.x, that uses exactly the same pattern):
public class MyIdentityUser<TKey> where TKey : IEquatable<TKey>
{
public virtual TKey Id { get; set; }
}
public class MyIdentityUserMapping<TKey> : ClassMapping<MyIdentityUser<TKey>>
where TKey : IEquatable<TKey>
{
public MyIdentityUserMapping()
{
Id(user => user.Id);
}
}
Thanks to the generic TKey
identifier, you can write framework code that uses equality checks without forcing a specific type at compile time (e.g string, int, long, Guid, etc.):
public Task<IdentityUser<TKey>> GetUserAsync(TKey id)
where TKey : IEquatable<TKey>
{
return (from user in _session.Query<IdentityUser<TKey>>()
where user.Id.Equals(id)
select user).FirstOrDefaultAsync();
}
Since EqualsGenerator
doesn't support this interface, all you get when trying to execute this method is an exception:
{System.NotSupportedException: Boolean Equals(System.Guid)
at NHibernate.Linq.Visitors.HqlGeneratorExpressionVisitor.VisitMethodCallExpression(MethodCallExpression expression)
at NHibernate.Linq.Visitors.HqlGeneratorExpressionVisitor.VisitExpression(Expression expression)
at NHibernate.Linq.Visitors.HqlGeneratorExpressionVisitor.Visit(Expression expression, VisitorParameters parameters)
at NHibernate.Linq.Visitors.QueryModelVisitor.VisitWhereClause(WhereClause whereClause, QueryModel queryModel, Int32 index)
at Remotion.Linq.Clauses.WhereClause.Accept(IQueryModelVisitor visitor, QueryModel queryModel, Int32 index)
at Remotion.Linq.QueryModelVisitorBase.VisitBodyClauses(ObservableCollection`1 bodyClauses, QueryModel queryModel)
at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
at NHibernate.Linq.Visitors.QueryModelVisitor.Visit()
at NHibernate.Linq.Visitors.QueryModelVisitor.GenerateHqlQuery(QueryModel queryModel, VisitorParameters parameters, Boolean root, Nullable`1 rootReturnType)
at NHibernate.Linq.NhLinqExpression.Translate(ISessionFactoryImplementor sessionFactory, Boolean filter)
at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
at NHibernate.Engine.Query.QueryExpressionPlan.CreateTranslators(IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)
at NHibernate.Engine.Query.QueryExpressionPlan..ctor(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters, ISessionFactoryImplementor factory)
at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)
at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)
at NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)
at NHibernate.Linq.DefaultQueryProvider.PrepareQuery(Expression expression, IQuery& query)
at NHibernate.Linq.DefaultQueryProvider.ExecuteAsync(Expression expression, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
at NHibernate.Linq.DefaultQueryProvider.<ExecuteAsync>d__22`1.MoveNext()
The fix is extremely simple: updating EqualsGenerator
to support IEquatable
:
public EqualsGenerator()
{
SupportedMethods = new[]
{
// ... existing methods ...
ReflectHelper.GetMethodDefinition<IEquatable<string>>(x => x.Equals(default(string))),
ReflectHelper.GetMethodDefinition<IEquatable<char>>(x => x.Equals(default(char))),
ReflectHelper.GetMethodDefinition<IEquatable<sbyte>>(x => x.Equals(default(sbyte))),
ReflectHelper.GetMethodDefinition<IEquatable<byte>>(x => x.Equals(default(byte))),
ReflectHelper.GetMethodDefinition<IEquatable<short>>(x => x.Equals(default(short))),
ReflectHelper.GetMethodDefinition<IEquatable<ushort>>(x => x.Equals(default(ushort))),
ReflectHelper.GetMethodDefinition<IEquatable<int>>(x => x.Equals(default(int))),
ReflectHelper.GetMethodDefinition<IEquatable<uint>>(x => x.Equals(default(uint))),
ReflectHelper.GetMethodDefinition<IEquatable<long>>(x => x.Equals(default(long))),
ReflectHelper.GetMethodDefinition<IEquatable<ulong>>(x => x.Equals(default(ulong))),
ReflectHelper.GetMethodDefinition<IEquatable<float>>(x => x.Equals(default(float))),
ReflectHelper.GetMethodDefinition<IEquatable<double>>(x => x.Equals(default(double))),
ReflectHelper.GetMethodDefinition<IEquatable<decimal>>(x => x.Equals(default(decimal))),
ReflectHelper.GetMethodDefinition<IEquatable<Guid>>(x => x.Equals(default(Guid))),
ReflectHelper.GetMethodDefinition<IEquatable<DateTime>>(x => x.Equals(default(DateTime))),
ReflectHelper.GetMethodDefinition<IEquatable<DateTimeOffset>>(x => x.Equals(default(DateTimeOffset))),
ReflectHelper.GetMethodDefinition<IEquatable<bool>>(x => x.Equals(default(bool)))
};
}
When these definitions are added, everything works like a charm.
If you guys agree with the change, I'd be happy to send a PR.