C# Nhibernate.Fetch调用在模拟会话上失败
我爱NHibernate(和NHibernate.Linq)。我不会过早地进行优化,但有时会遇到非常严重的N+1问题。对于N+1,建议使用NH的C# Nhibernate.Fetch调用在模拟会话上失败,c#,unit-testing,nhibernate,linq-to-nhibernate,C#,Unit Testing,Nhibernate,Linq To Nhibernate,我爱NHibernate(和NHibernate.Linq)。我不会过早地进行优化,但有时会遇到非常严重的N+1问题。对于N+1,建议使用NH的Fetch扩展方法 当我创建会话的模拟时,问题就出现了。我将创建一个列表,并将我的mock设置为在有人调用\u session.Query()时返回列表。当我向查询(即\u session.query().Fetch(u=>u.Address)添加Fetch调用时,我会收到以下错误消息: There is no method 'Fetch' on typ
Fetch
扩展方法
当我创建会话的模拟时,问题就出现了。我将创建一个列表
,并将我的mock设置为在有人调用\u session.Query()
时返回列表。当我向查询(即\u session.query().Fetch(u=>u.Address)
添加Fetch
调用时,我会收到以下错误消息:
There is no method 'Fetch' on type 'NHibernate.Linq.EagerFetchingExtensionMethods'
that matches the specified arguments
NHibernate的fetch接受普通的IQueryable
,但尝试将其转换为特定的NH实现,如果不能,则失败
如果在非NH实现(即列表)上调用它,我真的希望Fetch
不会出错,只是被忽略了,这样我仍然可以在单元测试中使用它。救命!好吧,我自己尝试过实现它,但感谢上帝,我找到了一个已经做了这些工作的人
你唯一需要做的事情就是急切地调用Fetch
,而不仅仅是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);
}
}
}
使用系统;
使用系统集合;
使用System.Collections.Generic;
使用System.Linq;
使用System.Linq.Expressions;
使用NHibernate.Linq;
使用Remotion.Linq;
命名空间LittleFish.Persistence.Extensions
{
///
///为NHibernate方法提供扩展方法包装
///允许使用源代码以避免“使用”NHibernate。
///
公共静态类NHibernateExtensions
{
///
///“渴望”加载指定可查询项的投影,
///引用映射的子对象。
///
公共静态IFetchRequest急切获取(
这是一个可以质疑的问题,
表达式(表达式)
{
if(queryable是QueryableBase)
返回FetchHelper.Create(queryable.Fetch(表达式));
其他的
返回FetchHelper.CreateNonNH(可查询);
}
///
///“渴望”加载指定可查询项的第二级投影,
///引用第一个已加载子项的映射子项。
///
公共静态IFetchRequest然后立即获取(
这个请求是可查询的,
表达式(表达式)
{
if(queryable是QueryableFetchHelper)
返回FetchHelper.CreateNonNH(可查询);
其他的
返回FetchHelper.Create(queryable.ThenFetch(表达式));
}
///
///“渴望”加载指定可查询项的投影,
///引用映射的子对象。
///
公共静态IFetchRequest急切地获取许多(
这是一个可以质疑的问题,
表达式(表达式)
{
if(queryable是QueryableBase)
返回FetchHelper.Create(queryable.FetchMany(表达式));
其他的
返回FetchHelper.CreateNonNH(可查询);
}
///
///“渴望”加载指定可查询项的第二级投影,
///引用第一个已加载子项的映射子项。
///
公共静态IFetchRequest然后急切地获取许多
(
这个请求是可查询的,
表达式(表达式)
{
if(queryable是QueryableFetchHelper)
返回FetchHelper.CreateNonNH(可查询);
其他的
返回FetchHelper.Create(queryable.ThenFetchMany(表达式));
}
}
///
///为NHibernate的FetchRequest接口提供包装,
///因此,运行即时加载查询的库不必引用
///NHibernate组件。
///
公共接口请求:
拒绝请求
{
}
内部类NhFetchHelper:IFetchRequest
{
私有只读INhFetchRequest realFetchRequest;
//这是NHibernate查询的真正交易
内部NhFetchHelper(INhFetchRequest realFetchRequest)
{
this.realFetchRequest=realFetchRequest;
}
公共IEnumerator GetEnumerator()
{
return(realFetchRequest).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return(realFetchRequest).GetEnumerator();
}
公开表达
{
获取{return(realFetchRequest).Expression;}
}
公共类型ElementType
{
获取{return(realFetchRequest).ElementType;}
}
公共IQueryProvider提供程序
{
获取{return(realFetchRequest).Provider;}
}
}
内部类QueryableFetchHelper:
IFetchRequest
{
私有只读可查询;
//用于非NH数据存储
内部QueryableFetchHelper(IQueryable queryable)
{
this.queryable=queryable;
}
公共IEnumerator GetEnumerator()
{
return(可查询).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return(可查询).GetEnumerator();
}
公开表达
{
获取{return(queryable).Expression;}
}
公共类型ElementType
{
获取{return(queryable).ElementType;}
}
公共IQueryProvider提供程序
{
获取{return(queryable).Provider;}
}
}
///
///FetchHelper的静态“前门”,一般工厂允许
///泛型类型推断。
///
内部静态类FetchHelper
{
公共静态NhFetch
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;
}
}
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;
}