网站首页 文章专栏 使用Mongodb的C#驱动自动备份数据
使用Mongodb的C#驱动自动备份数据
发布 作者:被打断de狗腿 浏览量:208
最近服务器要到期了,得想想数据迁移的问题,掉了不少头发,所以才有这文章

最近准备给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)!);
        }
    }
}
loading