网站首页 文章专栏 使用Mongodb的C#驱动自动备份数据
最近服务器要到期了,得想想数据迁移的问题,掉了不少头发,所以才有这文章
最近准备给Mongodb自动备份,但是并不是所有的 集合 、 数据 都需要备份,所以没辙,自己开发。
首先自定义一个特性 BackupAttribute
,用来标记需要备份的集合。按照我的系统约定,实体名称和Mongodb集合名称是一样的,所以这个特性标记在需要备份的实体类上就好。
// 因为实体类,所以只能作用在类、结构体上
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class BackupAttribute:Attribute
{
}
自定义特性定义好了,需要备份的集合标记上就可以了,但是我需要的并不是全量备份,所以这里需要引入 Dynamic LINQ NuGet包。使用动态LINQ筛选数据进行备份。
Install-Package System.Linq.Dynamic.Core
接下来改造 BackupAttribute
,并在需要备份的实体上标记
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class BackupAttribute:Attribute
{
public string Where { get; set; }
}
// 标记需要备份的集合、实体,并筛选数据
[Backup(Where = "x => Status != Dpz.Core.EnumLibrary.ExecuteStatus.ExecuteComplete")]
public class WaitExecution:BaseEntity
{
/// <summary>
/// 执行类别
/// </summary>
[BsonRepresentation(BsonType.String)]
public ExecuteCategory Category { get; set; }
/// <summary>
/// 执行状态
/// </summary>
[BsonRepresentation(BsonType.String)]
public ExecuteStatus Status { get; set; }
/// <summary>
/// 执行方案
/// </summary>
[BsonRepresentation(BsonType.String)]
public ExecuteScheme Scheme { get; set; }
/// <summary>
/// 关联
/// </summary>
public string Relation { get; set; }
public DateTime? CreateTime { get; set; }
public DateTime? LastUpdateTime { get; set; }
}
// 缺省 Where 属性的,进行全量备份
[Backup]
public class Video:IBaseEntity
{
public string Id { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string VideoTitle { get; set; }
public string SubTitle { get; set; }
public string Description { get; set; }
public string[] Tags { get; set; }
}
通过反射获取所有需要备份的集合,详情参见 实体
public class BackupType
{
public Type? Type { get; set; }
public BackupAttribute? Attribute { get; set; }
}
var types = Assembly.Load("Dpz.Core.Public.Entity").GetExportedTypes().Select(x =>
{
var attribute = x.GetCustomAttribute<BackupAttribute>();
if (attribute != null)
{
return new BackupType
{
Type = x,
Attribute = attribute
};
}
return null;
}).Where(x => x != null).ToList();
根据具体类型反射获取 Repository<>
的SearchFor
方法,然后使用Dynamic LINQ 生成lambda表达式,执行备份查询
foreach (var type in types)
{
if (type is {Type: null}) continue;
var lambda =
DynamicExpressionParser.ParseLambda(
parsingConfig: ParsingConfig.Default,
createParameterCtor: false,
itType: type.Type,
resultType: null,
// 如果 Where 没有内容,那么就全量查询
expression: !string.IsNullOrEmpty(type.Attribute?.Where) ? type.Attribute.Where : "x => true");
var repositoryType = typeof(Repository<>);
// 泛型 Repository<> 的具体类型
var instanceType = repositoryType.MakeGenericType(type.Type);
// 实例化
var repository = Activator.CreateInstance(instanceType, DbTools.DefaultOption);
// 获取 SearchFor 方法
var searchForMethod = (from x in instanceType.GetMethods()
let parameters = x.GetParameters()
where x.Name == "SearchFor" && parameters.Length == 1 && parameters[0].Name == "predicate"
select x).FirstOrDefault();
if (repository != null && searchForMethod != null)
{
// 执行方法
var returnValue = searchForMethod.Invoke(repository, new object?[] {lambda}) as IMongoQueryable;
// 获取备份数据
var data = await returnValue?.ToDynamicListAsync()!;
if (data.Any())
{
// 将备份数据保存成文件
await using var stream = new FileStream(
Path.Combine("path", type.Type.Name + ".json"),
FileMode.Create, FileAccess.Write);
await using var writer = new StreamWriter(stream);
// 这里使用mongodb自带的序列化器,使一些多态类型能序列化、反序列化
await writer.WriteAsync(data.ToJson());
}
}
}
有了备份数据,还原数据和上述差不多。也是反射获取所有需要备份的实体类型,反射获取 Repository<>
的InsertAsync
方法,读取备份数据,执行方法,插入数据。
foreach (var type in types)
{
if (type is {Type: null}) continue;
var dataPath = Path.Combine("RestorePath", $"{type.Type.Name}.json");
if(!File.Exists(dataPath)) continue;
var repositoryType = typeof(Repository<>);
var instanceType = repositoryType.MakeGenericType(type.Type);
var repository = Activator.CreateInstance(instanceType, dbOption);
var insertMethod = (from x in instanceType.GetMethods()
let parameters = x.GetParameters()
where x.Name == "InsertAsync" && parameters.Length == 1 && parameters[0].Name == "source" && parameters[0].ParameterType.IsInterface
select x).FirstOrDefault();
if (repository != null && insertMethod != null)
{
var listType = typeof(List<>).MakeGenericType(type.Type);
var fileStream = new FileStream(dataPath, FileMode.Open);
using var reader = new StreamReader(fileStream);
var data = BsonSerializer.Deserialize(reader, listType);
if (data != null)
{
await ((insertMethod.Invoke(repository, new[] { data }) as Task)!);
}
}
}