An open API service indexing awesome lists of open source software.

https://github.com/chenxyzl/mongoincupdate.fody

mongo Incremental update(include dictionary)
https://github.com/chenxyzl/mongoincupdate.fody

incremental-update mongo

Last synced: about 2 months ago
JSON representation

mongo Incremental update(include dictionary)

Awesome Lists containing this project

README

          

# PropertyChangeWatch.Fody
# mongo增量方案
1. 给所有属性在编译器静态注入脏标记
2. 集合类型:Dictionary用StateMap替换,暂不支持List(会退化为全量保存).
3. mongo存储时候检查藏标记来生成UpdateDefinition,并执行UpdateOneAsync保存

## 实现原理(如不关心直接看底部如何使用)
### 实现增量更新的脏标记注入
1. 引用Fody包,增加FodyWeavers.xml,配置导入MongoIncUpdate(MongoIncUpdate工程为Fody的静态代码编织插件,增量方案的代码注入在这里实现)
2. MongoIncUpdate工程的增加类ModuleWeaver(继承BaseModuleWeaver),会被fody调用(原理就是msbuild会在编译以前扫描引入的包的.targets文件,并执行其中的task,详情看《07_.net fody.md》)
1. ModuleWeave实现扫描标记为MongoIncUpdateAttribute为等待被注入的对象
2. ModuleWeave实现扫描标记为MongoIncUpdateInterfaceAttribute为被注入的接口(基于插件模型)
3. 对每个MongoIncUpdateAttribute标记的对象分别注入MongoIncUpdateInterfaceAttribute标记的接口
4. 对每个MongoIncUpdateAttribute标记的对象分别注入接口的属性(Dirties,IdxMapping,NameMapping)实现
5. 对每个MongoIncUpdateAttribute标记的对象分别注入_dirties,_idxMapping,_nameMapping字段
6. 对每个MongoIncUpdateAttribute标记的对象分别注入set/get_Dirties,set/get_IdxMapping,set/get_NameMapping字段
7. 对上述注入的setter和getter分别注入消息体,并在setter中注入调用MongoIncUpdateInterfaceAttribute标记的插件类的静态方法PropChange。实现了属性变化通知
8. 扫描对象的所有ctor方法,注入调用MongoIncUpdateInterfaceAttribute标记的插件类的实例方法Init
3. 实现StateMap和StateMapSerializer增加对Dictionary的支持

### Mongo增量的存储过程
1. 存储对象调用MongoIncUpdateInterfaceAttribute注入的接口的BuildUpdate来生成UpdateDefinition
2. BuildUpdate中for循环遍历脏标记,更具标记的位置来获取对象的值和类型相关属性
3. 如果是脏则整体存储,如果不是脏则检查是否是引用类型(string特殊引用类型除外),如果类型没有被注入插件接口则退化为整体存储(保底措施),有递归调用BuildUpdate
4. 增加StateMap(继承至MongoIncUpdateInterfaceAttribute标记的接口)来支持集合类型,(实现了StateMapSerializer来支持序列化反序列化)
这里注意dic[key]=value时候,mongo的序列化有个大坑
1. 在StringFieldDefinitionHelper.Resolve获取类型时候对于正常的obj.property = value中, value的类型是来至于obj的member中同名property的类型获取出来的。
2. 在obj[key]=value中。也会变为obj.key=value形式,走上述类型推断逻辑,会变成获取obj中成员同名为key的类型,而字典的key最终都为string类型。接下来又两个错误
1. key为全部数值类型,尝试转化为IBsonArraySerializer且失败,直接跳出类型推断代码,在上层退化为默认类型。
2. 非全数值类型,尝试转化为IBsonDocumentSerializer类型,而key一般为string类型,转换失败,直接跳出类型推断代码,在上层退化为默认类型。
3. 解决方法
1. StateMapSerializer的序列化时候把key转换为k_key的形式,避免上述中全数值类型被终止类型插件
2. 接着实现IBsonDocumentSerializer接口,在TryGetMemberSerializationInfo函数中返回value的序列化器。key的序列化器是StateMapSerializer,不会打断递归,且key会被检查继承关系,检查失败会退化为原生类型,能保证key正常序列化。对于value:此操作类似把value当作了key的成员类型。
5. 调用await collection.UpdateOneAsync(filter, setter, new UpdateOptions { IsUpsert = true });来保存

## 实现过程的伪代码
``` c#
public sealed NeedInject //: IDiffUpdateable //0.注入接口IDiffUpdateable
{
//1.构造函数注入调用IDiffUpdateable.Init()
// NeedInject(){
// //构造函数注入调用IDiffUpdateable.Init()
// }
//2.注入属性Dirties
//覆盖属性 IDiffUpdateable.Dirties
//private static readonly BitArray _dirties = new(0);
//BitArray IDiffUpdateable.Dirties { get; set; } = _dirties;
//3.注入静态成员IsOnceInitDone
//覆盖 IDiffUpdateable.IsOnceInitDone
// private static readonly bool _isOnceInitDone = false; //跳过初始化逻辑
// bool IDiffUpdateable.IsOnceInitDone { get; set; } = _isOnceInitDone;
//4.注入静态成员NameMapping
//覆盖 IDiffUpdateable.NameMapping
// private static readonly Dictionary _nameMapping = new();
// Dictionary IDiffUpdateable.NameMapping { get; set; } = _nameMapping;
//5.注入静态成员NameMapping
//覆盖 IDiffUpdateable.IdxMapping
// private static readonly Dictionary _idxMapping = new();
// Dictionary IDiffUpdateable.IdxMapping { get; set; } = _idxMapping;
public int PropA { get; set;} //注入setter调用IDiffUpdateable.PropChange
}
```

## 如何使用
### 1.注册序列化器(因为嵌套类型需要)
最好在链接mongo数据库之前
BsonSerializer.RegisterGenericSerializerDefinition(typeof(StateMap<,>), typeof(StateMapSerializer<,>));

### 2.存储实体构造
``` C#
[MongoIncUpdate]
public class Inner2
{
//多层嵌套 任意测试了
public int I { get; set; }
[BsonIgnore]
public int XX { get; set; }
}

[MongoIncUpdate]
public class Inner1
{
//测试嵌套的dictionary的引用类型嵌套
[BsonSerializer(typeof(StateMapSerializer))]
public StateMap Dic1 { get; set; } = new();

public Inner2 Inner2 { get; set; } = new();
}

[MongoIncUpdate]
public class Item
{
//id
[BsonId] public int Id { get; set; }

//string类型带attr
[BsonElement("RealName")] public string Name { get; set; }
//
//测试引用类型
public Inner1 Inner1 { get; set; } = new();
//
// //测试dictionary的值类型
[BsonSerializer(typeof(StateMapSerializer))]
public StateMap Dic1 { get; set; } = new();

//测试dictionary的引用类型嵌套
[BsonSerializer(typeof(StateMapSerializer))]
public StateMap Dic2 { get; set; } = new();
}
```

### 3.保存数据
```c#
public static async Task SaveIm(this Item self, IMongoCollection collection)
{
var diffUpdateable = self as IDiffUpdateable;
var defs = new List>();
diffUpdateable?.BuildUpdate(defs, "");
if (defs.Count == 0) return;
var setter = Builders.Update.Combine(defs);
var filter = Builders.Filter.Eq("_id", self.Id);
await collection.UpdateOneAsync(filter, setter, new UpdateOptions { IsUpsert = true });
Console.WriteLine($"update data count:{defs.Count}");
}
```

## 性能测试
Benchmark.md[链接](./Benchmark.md)