我喜欢NHibernate(以及NHibernate.Linq)。我没有过早地优化,但有时我会遇到一个非常令人讨厌的N + 1问题。 N + 1的推荐修复方法是使用NH的Fetch
扩展方法。
当我创建ISession
的模拟时出现问题。我会创建一个List<User>
并设置我的模拟,以便在有人调用_session.Query<User>()
时返回列表。当我向查询添加Fetch
次调用时(即_session.Query<User>().Fetch(u => u.Address)
),我收到以下错误消息:
There is no method 'Fetch' on type 'NHibernate.Linq.EagerFetchingExtensionMethods'
that matches the specified arguments
NHibernate的fetch接受一个普通的IQueryable<T>
但尝试将其作为特定的NH实现进行转换,如果不能,则会失败。
如果在非NH实现(即列表)上调用它,我真的希望Fetch
不出错,只是被忽略,所以我仍然可以在我的单元测试中使用它。救命啊!
答案 0 :(得分:4)
嗯,我试图自己实现这个,但感谢上帝,我找到了一个已经做过腿部工作的人。
http://mycodinglife.blog.com/2013/06/10/fetch-good-boy-now-play-nice-with-my-unit-tests/#
您唯一需要做的就是致电EagerlyFetch
,而不仅仅是Fetch
。
我已经复制了下面的相关代码,因为他的博客已经有相当数量的http 500错误和css问题。我不认为它正在被维护。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NHibernate.Linq;
using Remotion.Linq;
namespace LittleFish.Persistence.Extensions
{
/// <summary>
/// Provides extension method wrappers for NHibernate methods
/// to allow consuming source code to avoid "using" NHibernate.
/// </summary>
public static class NHibernateExtensions
{
/// <summary>
/// Eager-loads a projection of the specified queryable,
/// referencing a mapped child object.
/// </summary>
public static IFetchRequest<T, TRel> EagerlyFetch<T, TRel>(
this IQueryable<T> queryable,
Expression<Func<T, TRel>> expression)
{
if (queryable is QueryableBase<T>)
return FetchHelper.Create(queryable.Fetch(expression));
else
return FetchHelper.CreateNonNH<T, TRel>(queryable);
}
/// <summary>
/// Eager-loads a second-level projection of the specified queryable,
/// referencing a mapped child of the first eager-loaded child.
/// </summary>
public static IFetchRequest<T, TRel2> ThenEagerlyFetch<T, TRel, TRel2>(
this IFetchRequest<T, TRel> queryable,
Expression<Func<TRel, TRel2>> expression)
{
if (queryable is QueryableFetchHelper<T, TRel>)
return FetchHelper.CreateNonNH<T, TRel2>(queryable);
else
return FetchHelper.Create(queryable.ThenFetch(expression));
}
/// <summary>
/// Eager-loads a projection of the specified queryable,
/// referencing a mapped child object.
/// </summary>
public static IFetchRequest<T, TRel> EagerlyFetchMany<T, TRel>(
this IQueryable<T> queryable,
Expression<Func<T, IEnumerable<TRel>>> expression)
{
if(queryable is QueryableBase<T>)
return FetchHelper.Create(queryable.FetchMany(expression));
else
return FetchHelper.CreateNonNH<T, TRel>(queryable);
}
/// <summary>
/// Eager-loads a second-level projection of the specified queryable,
/// referencing a mapped child of the first eager-loaded child.
/// </summary>
public static IFetchRequest<T, TRel2> ThenEagerlyFetchMany
<T, TRel, TRel2>(
this IFetchRequest<T, TRel> queryable,
Expression<Func<TRel, IEnumerable<TRel2>>> expression)
{
if (queryable is QueryableFetchHelper<T, TRel>)
return FetchHelper.CreateNonNH<T, TRel2>(queryable);
else
return FetchHelper.Create(queryable.ThenFetchMany(expression));
}
}
/// <summary>
/// Provides a wrapper for NHibernate's FetchRequest interface,
/// so libraries that run eager-loaded queries don't have to reference
/// NHibernate assemblies.
/// </summary>
public interface IFetchRequest<TQuery, TFetch> :
INhFetchRequest<TQuery, TFetch>
{
}
internal class NhFetchHelper<TQuery, TFetch> : IFetchRequest<TQuery, TFetch>
{
private readonly INhFetchRequest<TQuery, TFetch> realFetchRequest;
//this is the real deal for NHibernate queries
internal NhFetchHelper(INhFetchRequest<TQuery, TFetch> realFetchRequest)
{
this.realFetchRequest = realFetchRequest;
}
public IEnumerator<TQuery> GetEnumerator()
{
return (realFetchRequest).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (realFetchRequest).GetEnumerator();
}
public Expression Expression
{
get { return (realFetchRequest).Expression; }
}
public Type ElementType
{
get { return (realFetchRequest).ElementType; }
}
public IQueryProvider Provider
{
get { return (realFetchRequest).Provider; }
}
}
internal class QueryableFetchHelper<TQuery, TFetch> :
IFetchRequest<TQuery, TFetch>
{
private readonly IQueryable<TQuery> queryable;
//for use against non-NH datastores
internal QueryableFetchHelper(IQueryable<TQuery> queryable)
{
this.queryable = queryable;
}
public IEnumerator<TQuery> GetEnumerator()
{
return (queryable).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (queryable).GetEnumerator();
}
public Expression Expression
{
get { return (queryable).Expression; }
}
public Type ElementType
{
get { return (queryable).ElementType; }
}
public IQueryProvider Provider
{
get { return (queryable).Provider; }
}
}
/// <summary>
/// The static "front door" to FetchHelper, with generic factories allowing
/// generic type inference.
/// </summary>
internal static class FetchHelper
{
public static NhFetchHelper<TQuery, TFetch> Create<TQuery, TFetch>(
INhFetchRequest<TQuery, TFetch> nhFetch)
{
return new NhFetchHelper<TQuery, TFetch>(nhFetch);
}
public static NhFetchHelper<TQuery, TFetch> Create<TQuery, TFetch>(
IFetchRequest<TQuery, TFetch> nhFetch)
{
return new NhFetchHelper<TQuery, TFetch>(nhFetch);
}
public static IFetchRequest<TQuery, TRel> CreateNonNH<TQuery, TRel>(
IQueryable<TQuery> queryable)
{
return new QueryableFetchHelper<TQuery, TRel>(queryable);
}
}
}
答案 1 :(得分:1)
Fetch是一种来自NHibernate.Linq.EagerFetchingExtensionMethods
的扩展方法,这就是为什么你不能嘲笑它。如果您接受原始生产代码的修改,则可以使用包装器。 Wrapper是你将进一步模拟的代码!
不是以流畅的方式调用Fetch(query.Fetch(...)
),而是可以调用包装器并将查询作为参考注入:
NHibernateExtensionsWrapper.Fetch(query, x => x.ChildTable).ToList();
如何实现这个包装器?
public class NHibernateExtensionsWrapper : INHibernateExtensionsWrapper
{
public INhFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(IQueryable<TOriginating> query,
Expression<Func<TOriginating, TRelated>> relatedObjectSelector)
{
return query.Fetch(relatedObjectSelector);
}
}
如何实现包装器模拟?
public class NHibernateExtensionsWrapperMock : INHibernateExtensionsWrapper
{
public INhFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector)
{
return (INhFetchRequest<TOriginating, TRelated>) new NhFetchRequest<TOriginating, TRelated>(query);
}
private class NhFetchRequest<TOriginating, TRelated> : INhFetchRequest<TOriginating, TRelated>
{
private readonly IQueryable<TOriginating> _query;
public NhFetchRequest(IQueryable<TOriginating> query)
{
_query = query;
}
public IEnumerator<TOriginating> GetEnumerator()
{
return _query.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Expression Expression => _query.Expression;
public Type ElementType => _query.ElementType;
public IQueryProvider Provider => _query.Provider;
}
}
答案 2 :(得分:0)
我想出了一个新的解决方法,因为我不想为我们在生产代码中使用的每个提取调用添加类型检查,或者不想围绕所有现有的.Fetch
调用进行更改。 / p>
.Fetch
失败的原因与.NET内部重写表达式树的原因有关。我无法弄清楚到底是怎么回事,但是它似乎取决于表达式.List<T>
的输入,它实际上是使用typeof(IQueryable<T>).IsAssignableFrom(typeof(List<T>))
来检查输入匹配项,该返回的结果为false。 >
我已经在使用包装器,而不是返回普通的List<T>
(取自this answer)来解决.ToFuture()
在单元测试中不起作用的问题。
从链接的答案代码中,我将以下内容添加到ExpressionTreeModifier
:
protected override Expression VisitMethodCall(MethodCallExpression node)
{
//Don't overwrite if fetch wasn't the method being called
if (!node.Method.Name.Equals(nameof(EagerFetchingExtensionMethods.Fetch))
&& !node.Method.Name.Equals(nameof(EagerFetchingExtensionMethods.FetchMany))
&& !node.Method.Name.Equals(nameof(EagerFetchingExtensionMethods.ThenFetch))
&& !node.Method.Name.Equals(nameof(EagerFetchingExtensionMethods.ThenFetchMany)))
{
return base.VisitMethodCall(node);
}
//Get the first argument to the Fetch call. This would be our IQueryable or an Expression which returns the IQueryable.
var fetchInput = node.Arguments[0];
Expression returnExpression;
switch (fetchInput.NodeType)
{
case ExpressionType.Constant:
//If the input was a constant we need to run it through VisitConstant to get the underlying queryable from NHibernateQueryableProxy if applicable
returnExpression = VisitConstant((ConstantExpression)fetchInput);
break;
default:
//For everything else just return the input to fetch
//This is covers cases if we do something like .Where(x).Fetch(x), here fetchInput would be another method call
returnExpression = fetchInput;
break;
}
return returnExpression;
}
这是在表达式执行之前重写表达式,并完全删除了Fetch
调用,因此我们永远不会以Linq内部结构来尝试调用它。