Сохраните документ с указанным номером члена с помощью 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
цикл работает.
Да ...: /
Здесь появляется второе решение Марка, где вы добавляете префикс ... Я собираюсь сохранить соглашение об именах по умолчанию.
Ваше здоровье!