【问题标题】:IMongoCollection<T> throws an when I pass in a predicate for Func<T, bool>当我传入 Func<T, bool> 的谓词时,IMongoCollection<T> 抛出一个
【发布时间】:2020-08-05 17:37:11
【问题描述】:

我正在尝试对 ASP.NET Core Web 控制器进行单元测试。下面是简单的数据访问的样子:

var database = configuration.GetSection("MongoDb:Database").Value;
var mongoSettings = new MongoCollectionSettings() { AssignIdOnInsert = true };
var users = client.GetDatabase(database).GetCollection<User>("User", mongoSettings);
var user = users.Find(u => u.EmailAddress == emailAddress).SingleOrDefault();

这很好用。它不容易进行单元测试(Find 不能用 Moq 模拟),我更喜欢间接层而不是我的数据访问,所以我尝试像这样重构为存储库类:

public interface IRepository<T>
{
    T SingleOrDefault(Func<T, bool> predicate);
}


public class MongoRepository<T> : IRepository<T>
{
    // ...
    public T SingleOrDefault(Func<T, bool> predicate)
    {
        var objects = client.GetDatabase(this.databaseName).GetCollection<T>(this.repositoryName, this.settings);
        var toReturn = objects.Find(o => predicate(o) == true).SingleOrDefault();
        return toReturn;
    }
}

当我将控制器更新为使用 MongoRepository&lt;User&gt; 时,它会抛出:

var usersRepo = new MongoRepository<User>(this.configuration, this.client);
var user = usersRepo.SingleOrDefault(u => u.EmailAddress == emailAddress);

具体来说,在存储库代码中,objects.Find(o =&gt; predicate(o) == true).SingleOrDefault(); 抛出异常:An exception of type 'System.InvalidOperationException' occurred in MongoDB.Driver.dll but was not handled in user code: 'Invoke(value(System.Func2[AutoDungeoners.Web.Models.User,System.Boolean]), {document}) 不受支持。'`

我不确定如何解决这个问题。我怀疑我的谓词定义为Func&lt;T, bool&gt; 不正确,因为如果我只调用objects.Find(o =&gt; true),它会毫无问题地返回第一个对象。

下面的完整异常堆栈。

An exception of type 'System.InvalidOperationException' occurred in MongoDB.Driver.dll but was not handled in user code: 'Invoke(value(System.Func`2[AutoDungeoners.Web.Models.User,System.Boolean]), {document}) is not supported.'
   at MongoDB.Driver.Linq.Translators.PredicateTranslator.GetFieldExpression(Expression expression)
   at MongoDB.Driver.Linq.Translators.PredicateTranslator.TranslateComparison(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
   at MongoDB.Driver.Linq.Translators.PredicateTranslator.TranslateComparison(BinaryExpression binaryExpression)
   at MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate(Expression node)
   at MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate(Expression node, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate[TDocument](Expression`1 predicate, IBsonSerializer`1 parameterSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.ExpressionFilterDefinition`1.Render(IBsonSerializer`1 documentSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.MongoCollectionImpl`1.CreateFindOperation[TProjection](FilterDefinition`1 filter, FindOptions`2 options)
   at MongoDB.Driver.MongoCollectionImpl`1.FindSync[TProjection](IClientSessionHandle session, FilterDefinition`1 filter, FindOptions`2 options, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.<>c__DisplayClass41_0`1.<FindSync>b__0(IClientSessionHandle session)
   at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSession[TResult](Func`2 func, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.FindSync[TProjection](FilterDefinition`1 filter, FindOptions`2 options, CancellationToken cancellationToken)
   at MongoDB.Driver.FindFluent`2.ToCursor(CancellationToken cancellationToken)
   at MongoDB.Driver.IAsyncCursorSourceExtensions.SingleOrDefault[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)
   at MongoDB.Driver.IFindFluentExtensions.SingleOrDefault[TDocument,TProjection](IFindFluent`2 find, CancellationToken cancellationToken)

【问题讨论】:

  • EmailAddress的类型是什么?只是出于好奇,如果您尝试简化为 var toReturn = objects.Find(predicate).SingleOrDefault(); 我不知道它是如何构建查询的,但我想知道它是否不喜欢您调用谓词而不是仅将其作为参数提供。
  • @ScottHannen EmailAddress 只是一个字符串。我认为objects.Find(predicate) 无法编译,因为它试图将System.Something.Expression&lt;Func&lt;User, bool&gt;&gt;&gt; 转换为Func&lt;User, bool&gt;
  • 在传递之前将Func 转换为表达式怎么样? stackoverflow.com/questions/767733/…
  • @ScottHannen 会导致相同的异常。

标签: c# mongodb asp.net-core mongodb-.net-driver


【解决方案1】:

你可以在这里使用的Find(...)方法的唯一实现是(docs)

public static IFindFluent<TDocument, TDocument> Find<TDocument>(this IMongoCollection<TDocument> collection, Expression<Func<TDocument, bool>> filter, FindOptions options = null)

here 描述了FuncExpression&lt;Func&gt; 之间的区别。基本上GetCollection返回一个对MongoDB集合的引用,Find方法在这里的作用是将表达式树转换成MongoDB查询。您的查询在数据库中执行的时刻是您调用SingleOrDefault() 的时候,这就是您将获得异常的地方。

由于 Func&lt;T,bool&gt;Expression&lt;Func&lt;T,bool&gt;&gt; 之间没有隐式转换,您尝试使用 o =&gt; predicate(o) == true 创建另一个表达式并使您的代码可编译,但如前所述 - MongoDB .NET 驱动程序将无法弄清楚如何翻译这样的表达式到 MongoDB 查询中。

您需要将您的实现更改为:

public T SingleOrDefault(Expression<Func<T, bool>> predicate)
{
    var objects = client.GetDatabase(this.databaseName).GetCollection<T>(this.repositoryName, this.settings);
    var toReturn = objects.Find(predicate).SingleOrDefault();
    return toReturn;
}

请记住,这仅适用于那些可以翻译成 MongoDB 查询语言的表达式。 u =&gt; u.EmailAddress == emailAddress 看起来不错。

【讨论】:

  • 你的解释很有道理。除了必须传入Func 而不是谓词的问题之外,此代码无法编译——predicate(o) 不适用于Func。另外,请参阅上面 Scott 的 cmets:转换为 Expression&lt;Func&lt;T, bool&gt;&gt; 会引发相同的异常。
  • @nightblade9 那是我的错,对不起 - 不要运行谓词只是传递表达式。修改了我的答案
  • 哇,这确实有效!我有点惊讶(我正在根据 cmets 做类似的事情 - 只是在内部将 Func&lt;T, bool&gt; 转换为 Expression&lt;Func&lt;T, bool&gt;&gt; 并抛出)。非常感谢!
猜你喜欢
  • 1970-01-01
  • 2013-03-09
  • 2011-03-18
  • 2021-08-25
  • 2021-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多