How to fix {document}.Id is not supported error

Viewed 8670

I have inheritance in my mongo domain objects:

public abstract class Entity : IEntity
{
   [BsonId]     
   [BsonRepresentation(BsonType.ObjectId)]
   public string Id { get; set; }
   [BsonIgnore]
   public abstract string CollectionName { get; }
}

[BsonDiscriminator(Required = true)]
[BsonKnownTypes(typeof(Sub1), typeof(Sub2), typeof(Sub3))]
public class Main:Entity
{
   public string Name {get; set;}
   public string Address {get; set;}
}

[BsonDiscriminator(SubType.Sub1)]
public class Sub1: Main, IWorkAttached
{}

[BsonDiscriminator(SubType.Sub2)]
public class Sub2: Main, IWorkAttached
{}

[BsonDiscriminator(SubType.Sub3)]
public class Sub3: Main
{}

public interface IWorkAttached
{
   public string WorkId {get; set;}
}

I have method for updating the entity:

public void Update(IWorkAttached entity)
{
   var col = _db.GetCollection<IWorkAttached>("ColName");
   var updDefinition = Builders<IWorkAttached>.Update
      .Set(t => t.Name, entity.Name)
      .Set(t => t.Address, entity.Address)
      .Set(t => t.WorkId, entity.WorkId) // interface property

   col.UpdateOne(t => t.Id == entity.Id, updDefinition)
}

The idea is to have one method for updating two types of entity(Sub1 and Sub2) and then I can use it for both entities:

var sub1 = new Sub1 {...};
var sub2 = new Sub2 {...};

Update(sub1);
Update(sub2);

But, it doesn't work. I get the following exception:

{document}.Id is not supported.

If I use specific type in the Update method instead of interface then all works fine. Is it possible to use mongo driver in this way?

Stacktrace:

   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.Translate(Expression node)
   at MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate(Expression node, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.MongoCollectionImpl`1.ConvertWriteModelToWriteRequest(WriteModel`1 model, Int32 index)
   at System.Linq.Enumerable.<SelectIterator>d__154`2.MoveNext()
   at MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.BatchHelper.<FindOrderedRuns>d__8.MoveNext()
   at MongoDB.Driver.Core.Misc.ReadAheadEnumerable`1.ReadAheadEnumerator.MoveNext()
   at MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.BatchHelper.<GetBatches>d__6.MoveNext()
   at MongoDB.Driver.Core.Operations.BulkMixedWriteOperation.<ExecuteAsync>d__39.MoveNext()
--- 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 MongoDB.Driver.OperationExecutor.<ExecuteWriteOperationAsync>d__3`1.MoveNext()
--- 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 MongoDB.Driver.MongoCollectionImpl`1.<ExecuteWriteOperationAsync>d__62`1.MoveNext()
--- 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 MongoDB.Driver.MongoCollectionImpl`1.<BulkWriteAsync>d__22.MoveNext()
--- 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 MongoDB.Driver.MongoCollectionBase`1.<UpdateOneAsync>d__47.MoveNext()
--- 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)
6 Answers

Make sure your properties have public getter & setter, this was the problem in my case.

I had faced the same issue. After debugging in MongoDB code, I saw that a different serializer is applied when using the interface instead of the specific type. In this case, DiscriminatedInterfaceSerializer is used as serializer for the interface, whereas BsonClassMapSerializer is used for the specific type.

With this serializer for the interface, the classmap of the implemententing class is not linked to the interface. It is therefore unaware that Id is the bson id field and should be mapped to the '_id' key. That's why the exception is raised.

The solution that worked for me is to use ImpliedImplementationInterfaceSerializer:

[BsonSerializer(typeof(ImpliedImplementationInterfaceSerializer<IEntity, Entity>))]
public interface IEntity 
{ 
   // snip...
}

This attribute register the serializer and defines to use the Entity's bson classmap for IEntity so that the Id property will be properly mapped. I found this solution thanks to @Robert Baker in his answer here: Deserialize object as an interface with MongoDB C# Driver

I got same problem in my project, but in another method, and my solution is in writing full filter instead of using t => t.Id == entity.Id:

var filter = Builders<Account>.Filter.Eq("_id", new ObjectId(accountId));
account = _accountsCollection.Find(filter).First();

Just FYI, same issue here is due to the unsupported data type or complex expression used in the Lambda expression.

var awakenUsers = await _db.Get<DataEntity.UserProfile>(u
    => validInviteeMobs.Contains(u.MobNo)
    && (DateTime.UtcNow - u.LastAccessTime) >= new TimeSpan(72, 0, 0));

System.InvalidOperationException: (11/27/2018 05:20:37 - {document}{LastAccessTime}) is not supported. at MongoDB.Driver.Linq.Translators.PredicateTranslator.GetFieldExpression(Expression expression)

After change it to sample:

var awakenDeadline = DateTime.UtcNow.AddHours(-72);

var awakenUsers = await _db.Get<DataEntity.UserProfile>(u
    => validInviteeMobs.Contains(u.MobNo)
    && u.LastAccessTime <= awakenDeadline);

It works well

For some reason I got this issue when I changed a struct into a class, and yes the method that called the class/struct implemented an interface. Solved it by changing back to a struct.

In my case, I had overriden the Equals operator. Mongo doesn't like this, so just moving it out into a lambda did the trick. Cheers!

public class User : EntityBase
{
    public Guid OrganizationID { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }

    public override bool Equals(object obj)
    {
        if (obj is User user)
            return this.ID == user.ID
                || string.Equals(this.UserName, user.UserName, StringComparison.InvariantCultureIgnoreCase);
        return false;
    }


}

public async Task<User> SaveUser(User user)
    {
        var repo = _crudRepositoryFactory.Get<User>();
        var exisitngUser = await repo.First(x => x == user);

        if (exisitngUser == null)
        {
            await repo.Create(user);
            return user;
        }
        var res = await repo.Update(user, x => x == user);
        return res.Item2;
    }

I changed it to

public async Task<User> SaveUser(User user)
    {
        var repo = _crudRepositoryFactory.Get<User>();
        var exisitngUser = await repo.First(x => x.ID == user.ID
            || (x.UserName == user.UserName && x.OrganizationID == user.OrganizationID));

        if (exisitngUser == null)
        {
            await repo.Create(user);
            return user;
        }
        var res = await repo.Update(user, x => x.ID == user.ID
            || (x.UserName == user.UserName && x.OrganizationID == user.OrganizationID));
        return res.Item2;
    }
Related