Сохраните документ с указанным номером члена с помощью protobuf-net и MongoDB.

Я где-то видел, что с драйвером Go MongoDB можно сохранить документ с порядковым номером вместо имени поля.
В итоге они получают вот что в базе данных:

      {
   "3": "foo",
   "10": 1,
   "33": 123456
   "107": {
    "2": "bar",
    "1": "foo"
   }
}

Мне нравится эта идея! Итак, я попытался найти способ сделать то же самое с драйвером MongoDB C#.
У меня есть код ниже, но я не уверен, что мне нужно взять из protobut-net, чтобы получить порядковый номер участника.

      var pack = new ConventionPack();
pack.AddMemberMapConvention("numbered", m => m.SetElementName( WHAT TO PUT HERE ));
ConventionRegistry.Register("numbered", pack, type => true);       

В SetElementNameпринимает строковый параметр.
Как я могу получить порядковый номер участника из protobuf-net?
Что-то вроде ...Member.Order.ToString()
Не знаю, хорошая ли идея в целом, но я хочу ее проверить.

Спасибо

3 ответа

Метаданные поля для protobuf-net доступны из RuntimeTypeModel API, например:

      var members = RuntimeTypeModel.Default[yourType].GetFields();
foreach (var member in members)
{
    Console.WriteLine($"{member.FieldNumber}: {member.Member.Name}");
}

В .FieldNumber дает номер поля protobuf, а .Memberдает значение соответствующего поля или свойства. Вы можете захотеть сделать некоторый уровень кеширования, если m => m.SetElementName( WHAT TO PUT HERE ) оценивается много раз для одного и того же m, так что вы не выполняете ненужную работу - но: прежде, чем вы это сделаете, просто добавьте сначала запись в лямбду и посмотрите, как часто она вызывается: если это не слишком часто, может быть, не беспокойтесь об этом.

Обратите внимание, что также есть поиск по MetaType что позволяет запросить MemberInfo:

      var member = RuntimeTypeModel.Default[yourType][memberInfo];

Повторите редактирование; в этой области:

      var members = RuntimeTypeModel.Default[typeof(T)].GetFields();
foreach (var member in members)
{
    memberMap.SetElementName(member.FieldNumber.ToString());
}

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

Отдельно стоит осложнение наследования; protobuf-net не реализует наследование простым способом - вместо этого ожидается , что базовый тип также будет [ProtoContract]и предназначен для определения для каждого производного типа; номера полей зависят от типа, что означает: и базовый тип, и производный тип могут юридически иметь поле 1. Если вам нужно описать наследование, и вы полны решимости использовать модель protobuf-net, тогда вам нужно будет справиться с этим. ; например, вы можете использовать номер в качестве префикса для каждого, поэтому Base.Id является "1", и если мы представим, что Todo имеет поле 5 в [ProtoInclude(...)], тогда Todo.Title может быть "5.10".

В качестве альтернативы: если вы активно не используете protobuf-net: может быть, просто используйте свой собственный атрибут для чисел? или обычно есть встроенный атрибут, который выбранный вами сериализатор будет использовать напрямую.

Хорошо сейчас! Итак, после некоторого расследования я пришел к простому способу сделать это с помощью Марка. В MongoDB вместо использования атрибутов для украшения моделей и их свойств можно использовать код внутри. Внутри этого класса я добавляю цикл foreach, который предоставил Марк, и правильные параметры, теперь мы можем использовать числа вместо имен.

На стороне клиента и на стороне сервера это тот же код:

      //Base Model ClassMap
BsonClassMap.RegisterClassMap<Base>(cm => 
{
    cm.AutoMap();
    foreach (var member in RuntimeTypeModel.Default[typeof(Base)].GetFields())
    {
        cm.MapMember(typeof(Base).GetMember(member.Member.Name)[0])
            .SetElementName(member.FieldNumber.ToString())
            .SetOrder(member.FieldNumber);
    }
});

//Todo Model ClassMap
BsonClassMap.RegisterClassMap<Todo>(cm => 
{
    cm.AutoMap();
    foreach (var member in RuntimeTypeModel.Default[typeof(Todo)].GetFields())
    {
        cm.MapMember(typeof(Todo).GetMember(member.Member.Name)[0])
             .SetElementName(member.FieldNumber.ToString())
             .SetOrder(member.FieldNumber);
    }
});        

это немного некрасиво, но вы можете переделать его.

Следует отметить, что MongoDB контролирует Id. В базе anything that represent the object id стали _id. То же самое, когда вы вставляете новый документ в базу данных _t поле добавляется, если вы используете Discriminator(Я не уверен, что это полностью связано). По сути, каждый член, начинающийся с подчеркивания, зарезервирован. См. Изображение ниже после запуска кода:

Вы можете обратиться к вопросу выше в разделе обновлений, чтобы узнать, соответствует ли этот результат моделям с заданными заказами (это действительно так).

Вот код, который я использую для вставки и запросов:

      // INSERT
var client = channel.CreateGrpcService<IBaseService<Todo>>();
var reply = await client.CreateOneAsync(
   new Todo
   {
      Title = "Some Title"
   }
);        
  
// FIND BY ID
var todoId = new UniqueIdentification { Id = "613c110a073055f0d87a0e27"};
var res = await client.GetById(todoId);
     
    
// FIND ONE BY QUERY FILTER REQUEST 
    ...
var filter = Builders<Todo>.Filter.Eq("10", "Some Title");
var filterString = filter.Render(documentSerializer, serializerRegistry);
    ...         

Последний выше - это запрос с номером ( "10") собственности Title. Но можно аналогичным образом запросить имя свойства, например:

      // FIND ONE BY QUERY FILTER REQUEST 
     ...
var filter = Builders<Todo>.Filter.Eq(e => e.Title, "Some Title");
var filterString = filter.Render(documentSerializer, serializerRegistry);
    ...      
     

Что замечательно в этом подходе, так это то, что эти BsonClassMap вызываются один раз на клиенте и / или сервере при их запуске.

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

      [BsonDiscriminator("Base", RootClass = true)]
[DataContract]
public abstract class Base
{
   [BsonId]
   [BsonRepresentation(BsonType.ObjectId)]
   [ProtoMember(1)]
   public string Id { get; set; }

   [BsonDateTimeOptions]
   [ProtoMember(2)]
   public DateTime CreatedDate { get; private set; } = DateTime.UtcNow;

   [BsonDateTimeOptions]
   [ProtoMember(3)]
   public DateTime UpdatedDate { get; set; } = DateTime.UtcNow;
}       
    
[ProtoContract]
public class Todo : Base
{
   [ProtoMember(1)]
   public string Title { get; set; }
   [ProtoMember(2)]
   public string Content { get; set; }
   [ProtoMember(3)]
   public string Category { get; set; }
}          

но будет три столкновения, если foreachцикл работает.
Да ...: /
Здесь появляется второе решение Марка, где вы добавляете префикс ... Я собираюсь сохранить соглашение об именах по умолчанию.

Ваше здоровье!

Другие вопросы по тегам