Timothy John Laird
Timothy John Laird

Reputation: 1121

Expression tree is not supported on UpdateOneAsync

On calling UpdateOneAsync, with this wrapper:

    public async Task<UpdateResult> UpdateDocument<T>(
        string sCollectionName, 
        Expression<Func<T, bool>> filter, 
        UpdateDefinition<T> update,
        bool bUpsert,
        System.Threading.CancellationToken cancellationToken
        )
    {
        IMongoDatabase db = _mongoClient.GetDatabase(_optionsMonitor.CurrentValue.databasename);

        IMongoCollection<T> collection = db.GetCollection<T>(sCollectionName);

        return await collection.UpdateOneAsync<T>(filter, update, new UpdateOptions() { IsUpsert = bUpsert }, cancellationToken);
    }

Like so:

private async Task<Models.Errors> UpdateDbOnSyncServerToBoardUpdate(
        CancellationToken cancel,
        MongoDB.Bson.BsonDocument bsonDocConfigurationToUpdate,
        DateTime dtUpdated,
        string sId,
        int iObjectId,
        string sAppName,
        string sModelName
        )
    {
        MongoDB.Driver.UpdateResult updateResult = null;
        Models.Errors errors = null;

        try
        {
            updateResult = await _db.UpdateDocument<Models.Database.NodeBoardModel>(
                Constants.NodeBoardCollectionName,
                node => node.Id == sId && 
                node.RemoteBoard.apps.SingleOrDefault(
                    app => app.appname == sAppName).objects.
                    SingleOrDefault(model => model.name == sModelName).config_docs.
                    Any(config => config.config_id == iObjectId),
                MongoDB.Driver.Builders<Models.Database.NodeBoardModel>.Update.
                Set(
                    node => node.RemoteBoard.apps[-1].objects[-1].config_docs[-1].config_doc, bsonDocConfigurationToUpdate).
                Set(
                    node => node.RemoteBoard.apps[-1].objects[-1].config_docs[-1].config_dt, dtUpdated),
                false,
                cancel
                );

I receive a NotSupportedException:

The expression tree is not supported: {document}{RemoteBoard}{apps}.SingleOrDefault(app => (app.appname == "eACM")).objects.SingleOrDefault(model => (model.name == "tag")).config_docs

I sense I am using a LINQ keyword the wrong way or in a way that's not supported by MongoDb but it's hard to tell exactly where the problem lies.

I can't make anything of the stack trace:

at MongoDB.Driver.Linq.Processors.EmbeddedPipeline.EmbeddedPipelineBinder.BindNonMethodCall(Expression node) at MongoDB.Driver.Linq.Processors.PipelineBinderBase1.BindPipeline(Expression node) at MongoDB.Driver.Linq.Processors.PipelineBinderBase1.BindMethodCall(MethodCallExpression node) at MongoDB.Driver.Linq.Processors.EmbeddedPipeline.EmbeddedPipelineBinder.Bind(Expression node, IBindingContext parent) at MongoDB.Driver.Linq.Processors.SerializationBinder.VisitMethodCall(MethodCallExpression node) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node) at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node) at MongoDB.Driver.Linq.Processors.SerializationBinder.VisitBinary(BinaryExpression node) at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor) at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node) at MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate[TDocument](Expression1 predicate, IBsonSerializer1 parameterSerializer, IBsonSerializerRegistry serializerRegistry) at MongoDB.Driver.MongoCollectionImpl1.ConvertWriteModelToWriteRequest(WriteModel1 model, Int32 index) at System.Linq.Enumerable.SelectIterator[TSource,TResult](IEnumerable1 source, Func3 selector)+MoveNext() at System.Collections.Generic.List1.AddEnumerable(IEnumerable1 enumerable) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source) at MongoDB.Driver.Core.Operations.BulkMixedWriteOperation..ctor(CollectionNamespace collectionNamespace, IEnumerable1 requests, MessageEncoderSettings messageEncoderSettings) at MongoDB.Driver.MongoCollectionImpl1.CreateBulkWriteOperation(IEnumerable1 requests, BulkWriteOptions options) at MongoDB.Driver.MongoCollectionImpl1.BulkWriteAsync(IClientSessionHandle session, IEnumerable1 requests, BulkWriteOptions options, CancellationToken cancellationToken) at MongoDB.Driver.MongoCollectionImpl1.UsingImplicitSessionAsync[TResult](Func2 funcAsync, CancellationToken cancellationToken) at MongoDB.Driver.MongoCollectionBase1.UpdateOneAsync(FilterDefinition1 filter, UpdateDefinition1 update, UpdateOptions options, Func3 bulkWriteAsync) at WebApplication.Services.ConcreteDatabase.UpdateDocument[T](String sCollectionName, Expression1 filter, UpdateDefinition1 update, Boolean bUpsert, CancellationToken cancellationToken) in C:\GIT\app-manager\APIMM\ServerLevelConfiguration\WebApplication\Services\ConcreteDatabase.cs:line 131 at WebApplication.Services.SyncBoardDatabaseBackgroundService.UpdateDbOnSyncServerToBoardUpdate(CancellationToken cancel, BsonDocument bsonDocConfigurationToUpdate, DateTime dtUpdated, String sId, Int32 iObjectId, String sAppName, String sModelName) in C:\GIT\app-manager\APIMM\ServerLevelConfiguration\WebApplication\Services\SyncBoardDatabaseBackgroundService.cs:line 353

Model classes:

public class NodeBoardModel
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }

    [BsonElement]
    public NodeBoardRemoteModel RemoteBoard { get; set; }
}

public class NodeBoardRemoteModel
{
    [BsonElement]
    public List<NodeBoardAppModel> apps { get; set; }
}

public class NodeBoardAppModel
{
    [BsonElement]
    public string appname { get; set; }

    [BsonElement]
    public List<NodeBoardObjectModel> objects { get; set; }
}

public class NodeBoardObjectModel
{
    [BsonElement]
    public string name { get; set; }

    [BsonElement]
    public List<NodeBoardObjectConfigurationModel> config_docs { get; set; }
}

public class NodeBoardObjectConfigurationModel
{
    [BsonElement]
    public BsonDocument config_doc { get; set; }

    [BsonElement]
    public DateTime config_dt { get; set; }

    [BsonElement]
    public int config_id { get; set; }
}

Upvotes: 1

Views: 3996

Answers (1)

mickl
mickl

Reputation: 49985

The problem starts when you're trying to build your Update statement. As you probably know -1 passed as an index will be translated to the $ positional operator. The documentation says that

The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value

Additionally you're trying to build your filtering condition using SingleOrDefault and .NET MongoDB driver is not able to translate that into any MongoDB query syntax operator.

How to fix that ?

Instead of using the positional operator you can try using the positional filtered operator syntax.

var filter = Builders<NodeBoardModel>.Filter.Eq(f => f.Id, sId);
var update = Builders<NodeBoardModel>.Update.Set("RemoteBoard.apps.$[app].objects.$[object].config_docs.$[configdoc].config_dt", dtUpdated);

var arrayFilters = new List<ArrayFilterDefinition>();
ArrayFilterDefinition<BsonDocument> appFilter = new BsonDocument("app.appname", new BsonDocument("$eq", sAppName));
ArrayFilterDefinition<BsonDocument> objectFilter = new BsonDocument("object.name", new BsonDocument("$eq", sModelName));
ArrayFilterDefinition<BsonDocument> configDocFilter = new BsonDocument("configdoc.config_id", new BsonDocument("$eq", iObjectId));

arrayFilters.AddRange(new[] { appFilter, objectFilter, configDocFilter });

var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };

var res = Col.UpdateOne(filter, update, updateOptions);

Upvotes: 4

Related Questions