Пользовательский десериализатор не работает при использовании позиционного оператора - PullRequest
0 голосов
/ 29 марта 2019

Я создаю собственный сериализатор для списка элементов, идея в том, что я буду избегать десериализации некоторых элементов на основе их значений.

Это прекрасно работает для большинства вызовов чтения / обновления, то есть

await collection.InsertOneAsync(entry);
await collection.FindAsync(x => x.Id == entry.Id);
await collection.FindOneAndUpdateAsync(x => x.Id == entry.Id, Builders<Outer>.Update.Set(x => x.Value, 5));

, но когда я выполняю вызов обновления, используя позиционный оператор,

var filter = Builders<Outer>.Filter.Where(x => x.Elements.Any(e => e.Id == elementId));
var update = Builders<Outer>.Update.Set(x => x.Elements[-1].Value, 5);
await collection.FindOneAndUpdateAsync(filter, update);

, происходит сбой со следующей ошибкой:

Unhandled Exception: System.ArgumentException: Property 'System.String Id' is not defined for type 'System.Object'
Parameter name: property
   at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
   at System.Linq.Expressions.Expression.MakeMemberAccess(Expression expression, MemberInfo member)
   at System.Linq.Expressions.MemberExpression.Update(Expression expression)
   at System.Linq.Expressions.ExpressionVisitor.VisitMember(MemberExpression node)
   at MongoDB.Driver.Linq.Processors.SerializationBinder.VisitMember(MemberExpression node)
   at System.Linq.Expressions.MemberExpression.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.Processors.BinderHelper.BindWhere(PipelineExpression pipeline, IBindingContext bindingContext, LambdaExpression lambda)
   at MongoDB.Driver.Linq.Processors.EmbeddedPipeline.MethodCallBinders.AnyBinder.Bind(PipelineExpression pipeline, EmbeddedPipelineBindingContext bindingContext, MethodCallExpression node, IEnumerable`1 arguments)
   at MongoDB.Driver.Linq.Processors.MethodInfoMethodCallBinder`1.Bind(PipelineExpression pipeline, TBindingContext bindingContext, MethodCallExpression node, IEnumerable`1 arguments)
   at MongoDB.Driver.Linq.Processors.CompositeMethodCallBinder`1.Bind(PipelineExpression pipeline, TBindingContext bindingContext, MethodCallExpression node, IEnumerable`1 arguments)
   at MongoDB.Driver.Linq.Processors.PipelineBinderBase`1.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 MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate[TDocument](Expression`1 predicate, IBsonSerializer`1 parameterSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.MongoCollectionImpl`1.CreateFindOneAndUpdateOperation[TProjection](FilterDefinition`1 filter, UpdateDefinition`1 update, FindOneAndUpdateOptions`2 options)
   at MongoDB.Driver.MongoCollectionImpl`1.FindOneAndUpdateAsync[TProjection](IClientSessionHandle session, FilterDefinition`1 filter, UpdateDefinition`1 update, FindOneAndUpdateOptions`2 options, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.<>c__DisplayClass55_0`1.<FindOneAndUpdateAsync>b__0(IClientSessionHandle session)
   at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
   at MongoFiltering.Program.Main(String[] args) in C:\Users\jeroen.vannevel\source\repos\MongoFiltering\MongoFiltering\Program.cs:line 51
   at MongoFiltering.Program.<Main>(String[] args)

Я уменьшил его доследующий образецИз того, что я могу сказать, ваш пользовательский десериализатор на самом деле не нуждается в какой-либо работе - только его наличие нарушает условие фильтрации.

Ниже приведены модели, которые я использую, и пустой пользовательский сериализатор.Я создал MCVE здесь , который использует Mongo2Go для создания демона Монго на лету - вы можете просто его запустить.

public class Outer
    {
        public string Id { get; set; }
        public int Value { get; set; }

        [BsonSerializer(typeof(ElementsSerializer))]
        public List<Element> Elements { get; set; }
    }

    public class Element
    {
        public string Id { get; set; }
        public int Value { get; set; }
    }

    public class ElementsSerializer : EnumerableSerializerBase<List<Element>>
    {
        public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, List<Element> value) => base.Serialize(context, args, value);

        public override List<Element> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => base.Deserialize(context, args);

        protected override void AddItem(object accumulator, object item) => ((List<Element>)accumulator).Add((Element)item);
        protected override object CreateAccumulator() => new List<Element>();
        protected override IEnumerable EnumerateItemsInSerializationOrder(List<Element> value) => value;
        protected override List<Element> FinalizeResult(object accumulator) => (List<Element>)accumulator;
    }

1 Ответ

1 голос
/ 29 марта 2019

Я нашел проблему. Похоже, есть два базовых класса для наследования: EnumerableSerializerBase<TValue> и EnumerableSerializerBase<TValue, TItem>. Если вы используете первый (который я сделал), он будет использовать простой object десериализатор . Однако при использовании другой перегрузки использует универсальный десериализатор .

Конечный десериализатор выглядит следующим образом и работает как задумано:

public class ElementsSerializer : EnumerableSerializerBase<List<Element>, Element>
{
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, List<Element> value) => base.Serialize(context, args, value);
    public override List<Element> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => base.Deserialize(context, args);
    protected override object CreateAccumulator() => new List<Element>();
    protected override List<Element> FinalizeResult(object accumulator) => (List<Element>)accumulator;
    protected override void AddItem(object accumulator, Element item) => ((List<Element>)accumulator).Add(item);
    protected override IEnumerable<Element> EnumerateItemsInSerializationOrder(List<Element> value) => value;
}

Без этого EmbeddedPipelineBinder зарегистрирует выражение для замены нашего лямбда-значения на результат вышеупомянутого сериализатора. Ака, мы бы заменили все значения лямбда-значения Element документом типа object. Когда наша лямбда затем пытается получить доступ к свойству .Id, она жалуется, что не имеет этого свойства для типа object - как правильно.

Причина, по которой это обнаружилось только при попытке получить доступ к вложенной коллекции, я полагаю, потому что начальный уровень использует PipelineBinder (обратите внимание на отсутствие Embedded ) который не имеет этой специальной логики десериализации массива.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...