.NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)

news2024/11/16 19:48:52

WebAppDbTest 项目准备

  • 项目准备
    • 1、.net cli 创建项目
    • 2、nuget 包引用和项目结构
      • 2.1、项目添加相关 nuget 包
      • 2.2、WebAppDbTest 项目结构
    • 3、项目代码说明
      • 3.1、CSharp/C# 类文件说明
      • 3.2、json 配置文件说明
    • 4、项目运行预览
  • 数据库 .db 文件准备
    • 1、创建 SQLite 数据库
      • 1.1、在 Windows 上安装 SQLite
      • 1.2、创建 SQLite 数据库
    • 2、创建 LiteDB 数据库
      • 2.1、LiteDB.Shell
      • 2.2、创建 LiteDB 数据库

项目准备

此处还是以默认的 WeatherForecast(天气预报) 的数据为例,分别对两种类型的数据库做相应的 crud 操作,并对比测试性能。

1、.net cli 创建项目

这里我们使用的 .net8 版本,.net cli 创建 WebAppDbTest 项目,执行命令如下:

dotnet new webapi -o WebAppDbTest --no-https -f net8.0

2、nuget 包引用和项目结构

2.1、项目添加相关 nuget 包

  <ItemGroup>
    <PackageReference Include="FreeSql" Version="3.2.805" />
    <PackageReference Include="FreeSql.Provider.Sqlite" Version="3.2.805" />
    <PackageReference Include="LiteDB.Async" Version="0.1.7" />
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
    <PackageReference Include="Serilog.Sinks.LiteDB" Version="1.0.29" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
  </ItemGroup>

2.2、WebAppDbTest 项目结构

左边部分为 Nuget 安装的依赖包,右边部分为项目整体目录结构。

WebAppDbTest

3、项目代码说明

3.1、CSharp/C# 类文件说明

1、控制器类(Controllers

  • LiteDbController.cs,针对 LiteDb 数据库的 CRUD 方法;
  • SqliteController.cs,针对 SQLite 数据库的 CRUD 方法;
  • WeatherForecastController.cs(项目默认的类);

2、模型类(Models

  • ActionExecTime.cs,记录方法执行时间;
  • AppLogs.cs,记录日志信息;
  • WeatherForecast.cs,天气预报数据模型;

3、服务类(Services

  • AppLogsServices.cs,提供日志写入相关方法;
using System.Text.Json;
using WebAppDbTest.Models;

namespace WebAppDbTest.Services;

/// <summary>
/// 接口规范定义
/// </summary>
public interface IAppLogsServices
{
    /// <summary>
    /// 写入日志信息
    /// </summary>
    /// <param name="logs"></param>
    /// <param name="logLevel"></param>
    /// <returns></returns>
    Task WriteLogAsync(AppLogs logs, LogLevel logLevel = LogLevel.Information);

    /// <summary>
    /// 模型数据序列化json字符串
    /// </summary>
    /// <typeparam name="TData"></typeparam>
    /// <param name="data"></param>
    /// <returns></returns>
    Task<string> JsonSerializeAsync<TData>(TData data);
}

/// <summary>
/// 接口规范实现
/// </summary>
public class AppLogsServices : IAppLogsServices
{
    #region 构造函数 DI
    private readonly ILogger<AppLogsServices> _logger;

    public AppLogsServices(ILogger<AppLogsServices> logger)
    {
        _logger = logger;
    }
    #endregion

    /// <summary>
    /// 写入日志信息
    /// </summary>
    /// <param name="logs"></param>
    /// <param name="logLevel"></param>
    /// <returns></returns>
    public async Task WriteLogAsync(AppLogs logs, LogLevel logLevel = LogLevel.Information)
    {
        logs.LogLevel = logLevel;
        string jsonLogs = await JsonSerializeAsync(logs);

        switch (logLevel)
        {
            case LogLevel.Trace:
                _logger.LogTrace(jsonLogs);
                break;
            case LogLevel.Debug:
                _logger.LogDebug(jsonLogs);
                break;
            case LogLevel.Information:
                _logger.LogInformation(jsonLogs);
                break;
            case LogLevel.Warning:
                _logger.LogWarning(jsonLogs);
                break;
            case LogLevel.Error:
                _logger.LogError(jsonLogs);
                break;
            case LogLevel.Critical:
                _logger.LogCritical(jsonLogs);
                break;
            case LogLevel.None:
                _logger.LogInformation(jsonLogs);
                break;
            default:
                _logger.LogInformation(jsonLogs);
                break;
        }
    }

    /// <summary>
    /// json 序列化
    /// </summary>
    /// <typeparam name="TData"></typeparam>
    /// <param name="data"></param>
    /// <returns></returns>
    public async Task<string> JsonSerializeAsync<TData>(TData data)
    {
        var options = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        };
        await using var stream = new MemoryStream();
        await JsonSerializer.SerializeAsync(stream, data, options);
        stream.Position = 0;
        using var reader = new StreamReader(stream);
        return await reader.ReadToEndAsync();
    }
}
  • WeatherForecastServices.cs,模拟天气预报的数据;
using LiteDB;
using LiteDB.Async;
using Mapster;
using System.Diagnostics;
using System.Linq.Expressions;
using WebAppDbTest.Models;

namespace WebAppDbTest.Services;

/// <summary>
/// 天气预报接口规范定义
/// </summary>
public interface IWeatherForecastServices
{
    /// <summary>
    /// 获取天气预报概要
    /// </summary>
    /// <returns></returns>
    string GetSummarie();

    /// <summary>
    /// 获取天气预报列表
    /// </summary>
    /// <param name="count"></param>
    /// <returns></returns>
    IEnumerable<WeatherForecast> GetWeatherForecasts(int count);

    #region about litedb crud
    Task<Guid> LiteDbAddSingleAsync<T>(string collectioName, T t);

    Task<int> LiteDbAddBulkAsync<T>(string collectioName, IEnumerable<T> list);

    Task<T> LiteDbGetSingleAsync<T>(string collectioName, Guid id);

    Task<IEnumerable<T>> LiteDbGetAllAsync<T>(string collectioName);

    Task<bool> LiteDbUpdateSingleAsync<T>(string collectioName, T t);

    Task<int> LiteDbUpdateBulkAsync<T>(string collectioName, IEnumerable<T> list);

    Task<bool> LiteDbDeleteSingleAsync<T>(string collectioName, Guid id);

    Task<int> LiteDbDeleteBulkAsync<T>(string collectioName, Expression<Func<T, bool>> predicate);
    #endregion

    #region about sqlite crud
    Task<Guid> SqliteAddSingleAsync<T>(T t) where T : BaseEntity;

    Task<int> SqliteAddBulkAsync<T>(IEnumerable<T> list) where T : BaseEntity;

    Task<T> SqliteGetSingleAsync<T>(Guid id) where T : BaseEntity;

    Task<IEnumerable<T>> SqliteGetAllAsync<T>() where T : BaseEntity;

    Task<bool> SqliteUpdateSingleAsync<T>(T t) where T : BaseEntity, new();

    Task<int> SqliteUpdateBulkAsync<T>(IEnumerable<T> list) where T : BaseEntity, new();

    Task<bool> SqliteDeleteSingleAsync<T>(Guid id) where T : BaseEntity;

    Task<int> SqliteDeleteBulkAsync<T>(List<Guid> ids) where T : BaseEntity;
    #endregion
}

/// <summary>
/// 天气预报接口规范实现,模拟天气预报的数据
/// </summary>
public class WeatherForecastServices : IWeatherForecastServices
{
    #region 构造函数 DI
    private readonly IAppLogsServices _logger;
    private readonly IConfiguration _configuration;
    private readonly IFreeSql _freeSql;
    private readonly IWebHostEnvironment _webHostEnvironment;

    public WeatherForecastServices(IAppLogsServices logger,
        IConfiguration configuration,
        IFreeSql freeSql,
        IWebHostEnvironment webHostEnvironment)
    {
        _logger = logger;
        _configuration = configuration;
        _freeSql = freeSql;
        _webHostEnvironment = webHostEnvironment;
    }
    #endregion

    #region 模拟数据
    /// <summary>
    /// 模拟天气情况摘要数据列表
    /// </summary>
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public string GetSummarie() => Summaries[Random.Shared.Next(Summaries.Length)];

    public IEnumerable<WeatherForecast> GetWeatherForecasts(int count)
    {
        if (count <= 0 || count > 1000) count = 1000;

        /** 等效代码如下
        return Enumerable.Range(1, count).Select(index => {
            int temperatureC = Random.Shared.Next(-20, 55);
            var wf = new WeatherForecast
            {
                Id = Guid.NewGuid(),
                //Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                Date = DateTime.Now.AddDays(index),
                TemperatureC = temperatureC,
                TemperatureF = 32 + (int)(temperatureC / 0.5556),
                Summary = GetSummarie()
            };
            return wf;
        }).ToArray();
        */

        return Enumerable.Range(1, count).Select(index => GetWeatherForecast(index)).ToArray();
    }

    private WeatherForecast GetWeatherForecast(int index) 
    {
        int temperatureC = Random.Shared.Next(-20, 55);
        var wf = new WeatherForecast
        {
            Id = Guid.NewGuid(),
            Date = DateTime.Now.AddDays(index),
            TemperatureC = temperatureC,
            TemperatureF = 32 + (int)(temperatureC / 0.5556),
            Summary = GetSummarie()
        };
        return wf;
    }

    #endregion

    private enum DbFileType { LiteDB, SQLite };

    private string GetConnString(int index, DbFileType dbFileType = DbFileType.LiteDB) 
    {
        string? dbFile = _configuration.GetSection($"DbConfig:{index}:DbFilePath").Value;
        string filePath = Path.Combine(_webHostEnvironment.ContentRootPath, dbFile);

        string dbConnString = string.Empty;
        switch (dbFileType)
        {
            case DbFileType.LiteDB:
                dbConnString = $"Filename={ filePath };Connection=shared;Password=123456";
                break;
            case DbFileType.SQLite:
                dbConnString = $"Data Source={ filePath };Version=3;Pooling=False;Max Pool Size=100";
                break;
            default:
                dbConnString = $"Filename={ filePath };Connection=shared;Password=123456";
                break;
        }

        return dbConnString;
    }

    private static readonly Stopwatch _sw = new();

    /// <summary>
    /// 记录信息
    /// </summary>
    /// <param name="ts">方法执行耗时,单位:毫秒/ms</param>
    /// <param name="appLogs"></param>
    /// <returns></returns>
    private async Task LiteDbWraiteInfoAsync(ActionExecInfo actionExecInfo, AppLogs appLogs)
    {
        // 记录操作方法执行的时间
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个集合和 MongoDB 一样的,类似关系数据库的表。
        var collection = db.GetCollection<ActionExecInfo>(nameof(ActionExecInfo));
        var item = await collection.InsertAsync(actionExecInfo);
        appLogs.ActionExecInfoId = item.AsGuid;

        // 记录日志
        await _logger.WriteLogAsync(appLogs);
    }

    #region About LiteDb CRUD
    public async Task<Guid> LiteDbAddSingleAsync<T>(string collectioName, T t)
    {
        _sw.Start();
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个集合和 MongoDB 一样的,类似关系数据库的表。
        var collection = db.GetCollection<T>(collectioName);
        var item = await collection.InsertAsync(t);
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "AddSingle",
            ExecTime = ts,
            Database = "litedb"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "AddSingle",
            ItemCount = 1,
            OperationInfo = $"[AddSingle] ==> 插入数据:1条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return item.AsGuid;
    }

    public async Task<int> LiteDbAddBulkAsync<T>(string collectioName, IEnumerable<T> list)
    {
        _sw.Start();
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个表和 MongoDB 一样的
        var collection = db.GetCollection<T>(collectioName);
        int rcount = await collection.InsertBulkAsync(list);
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "AddBulk",
            ExecTime = ts,
            Database = "litedb"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "AddBulk",
            ItemCount = 1,
            OperationInfo = $"[AddBulk] ==> 插入数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return rcount;
    }

    public async Task<T> LiteDbGetSingleAsync<T>(string collectioName, Guid id) 
    {
        _sw.Start();
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个集合和 MongoDB 一样的,类似关系数据库的表。
        var collection = db.GetCollection<T>(collectioName);
        var result = await collection.FindByIdAsync(id); // 下面代码等效
        // var item = await collection.FindOneAsync(x => x.Id == id);
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "GetSingle",
            ExecTime = ts,
            Database = "litedb"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "GetSingle",
            ItemCount = 1,
            OperationInfo = $"[GetSingle] ==> 查询数据:1条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return result;
    }

    public async Task<IEnumerable<T>> LiteDbGetAllAsync<T>(string collectioName)
    {
        _sw.Start();
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个集合和 MongoDB 一样的,类似关系数据库的表。
        var collection = db.GetCollection<T>(collectioName);
        var result = await collection.FindAllAsync();
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "GetAll",
            ExecTime = ts,
            Database = "litedb"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "GetAll",
            ItemCount = result.Count(),
            OperationInfo = $"[GetAll] ==> 查询数据:{result.Count()}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return result;
    }

    public async Task<bool> LiteDbUpdateSingleAsync<T>(string collectioName, T t) 
    {
        _sw.Start();
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个集合和 MongoDB 一样的,类似关系数据库的表。
        var collection = db.GetCollection<T>(collectioName);
        bool isOk = await collection.UpdateAsync(t);
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "UpdateSingle",
            ExecTime = ts,
            Database = "litedb"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "UpdateSingle",
            ItemCount = 1,
            OperationInfo = $"[UpdateSingle] ==> 更新数据:1条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return isOk;
    }

    public async Task<int> LiteDbUpdateBulkAsync<T>(string collectioName, IEnumerable<T> list)
    {
        _sw.Start();
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个集合和 MongoDB 一样的,类似关系数据库的表。
        var collection = db.GetCollection<T>(collectioName);
        int rcount = await collection.UpdateAsync(list);
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "UpdateBulk",
            ExecTime = ts,
            Database = "litedb"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "UpdateBulk",
            ItemCount = rcount,
            OperationInfo = $"[UpdateBulk] ==> 更新数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return rcount;
    }

    public async Task<bool> LiteDbDeleteSingleAsync<T>(string collectioName, Guid id) 
    {
        _sw.Start();
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个集合和 MongoDB 一样的,类似关系数据库的表。
        var collection = db.GetCollection<T>(collectioName);
        bool isOk = await collection.DeleteAsync(id);
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "DeleteSingle",
            ExecTime = ts,
            Database = "litedb"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "DeleteSingle",
            ItemCount = 1,
            OperationInfo = $"[DeleteSingle] ==> 删除数据:1条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return isOk;
    }

    public static BsonValue Serialize(Guid id) => new BsonDocument(new Dictionary<string, BsonValue>
    {
        {"_id", id }
    });

    public static Guid Deserialize(BsonValue bsonValue)
    {
        var id = bsonValue["_id"].AsGuid;
        return id;
    }

    public async Task<int> LiteDbDeleteBulkAsync<T>(string collectioName, Expression<Func<T, bool>> predicate) 
    {
        _sw.Start();
        string connectionString = GetConnString(0);
        //打开数据库,如果不存在会自动创建。
        using var db = new LiteDatabaseAsync(connectionString);
        //打开一个集合和 MongoDB 一样的,类似关系数据库的表。
        var collection = db.GetCollection<T>(collectioName);
        //int rcount = await collection.DeleteAllAsync();
        int rcount = await collection.DeleteManyAsync(predicate);
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "DeleteBulk",
            ExecTime = ts,
            Database = "litedb"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "DeleteBulk",
            ItemCount = rcount,
            OperationInfo = $"[DeleteBulk] ==> 删除数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return rcount;

    }
    #endregion


    #region About SQLite CRUD
    public async Task<Guid> SqliteAddSingleAsync<T>(T t) where T : BaseEntity
    {
        _sw.Start();
        var rcount = await _freeSql.Insert(t).ExecuteAffrowsAsync();
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "AddSingle",
            ExecTime = ts,
            Database = "sqlite"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "AddSingle",
            ItemCount = rcount,
            OperationInfo = $"[AddSingle] ==> 插入数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return t.Id;
    }

    public async Task<int> SqliteAddBulkAsync<T>(IEnumerable<T> list) where T : BaseEntity
    {
        _sw.Start();
        int rcount = await _freeSql.Insert(list).ExecuteAffrowsAsync();
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "AddBulk",
            ExecTime = ts,
            Database = "sqlite"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "AddBulk",
            ItemCount = 1,
            OperationInfo = $"[AddBulk] ==> 插入数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return rcount;
    }

    public async Task<T> SqliteGetSingleAsync<T>(Guid id) where T : BaseEntity
    {
        _sw.Start();
        var result = await _freeSql.Select<T>().Where(x => x.Id == id).FirstAsync();
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "GetSingle",
            ExecTime = ts,
            Database = "sqlite"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "GetSingle",
            ItemCount = 1,
            OperationInfo = $"[GetSingle] ==> 查询数据:1条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return result;
    }

    public async Task<IEnumerable<T>> SqliteGetAllAsync<T>() where T : BaseEntity
    {
        _sw.Start();
        var result = await _freeSql.Select<T>().ToListAsync();
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "GetAll",
            ExecTime = ts,
            Database = "sqlite"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "GetAll",
            ItemCount = result.Count(),
            OperationInfo = $"[GetAll] ==> 查询数据:{result.Count()}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return result;
    }

    public async Task<bool> SqliteUpdateSingleAsync<T>(T t) where T : BaseEntity, new()
    {
        _sw.Start();

        // 推荐快照模式
        var repo = _freeSql.GetRepository<T>();
        var item = new T { Id = t.Id };
        repo.Attach(item); //此时快照 item
        t.Adapt(item);

        //bool isOk = ReferenceEquals(item, t);
        int rcount = await repo.UpdateAsync(item); //对比快照时的变化

        // 传统模式
        // int rcount = await _freeSql.Update<T>().SetSource(t).IgnoreColumns(a => new { a.Id }).ExecuteAffrowsAsync();

        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "UpdateSingle",
            ExecTime = ts,
            Database = "sqlite"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "UpdateSingle",
            ItemCount = rcount,
            OperationInfo = $"[UpdateSingle] ==> 更新数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return rcount > 0;
    }

    public async Task<int> SqliteUpdateBulkAsync<T>(IEnumerable<T> list) where T : BaseEntity, new()
    {
        _sw.Start();

        // 推荐快照模式
        var repo = _freeSql.GetRepository<T>();
        var items = list.Select(x => new T{ Id = x.Id });
        repo.Attach(items); //此时快照 item
        //list.Adapt(items);
        items = list;
        bool isOk = ReferenceEquals(items, list);
        int rcount = await repo.UpdateAsync(items); //对比快照时的变化

        // 传统模式
        //int rcount = await _freeSql.Update<T>().SetSource(list).IgnoreColumns(a => new { a.Id }).ExecuteAffrowsAsync();

        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "UpdateBulk",
            ExecTime = ts,
            Database = "sqlite"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "UpdateBulk",
            ItemCount = rcount,
            OperationInfo = $"[UpdateBulk] ==> 更新数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return rcount;
    }

    public async Task<bool> SqliteDeleteSingleAsync<T>(Guid id) where T : BaseEntity
    {
        _sw.Start();
        int rcount = await _freeSql.Delete<T>().Where(x => x.Id == id).ExecuteAffrowsAsync();
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "DeleteSingle",
            ExecTime = ts,
            Database = "sqlite"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "DeleteSingle",
            ItemCount = rcount,
            OperationInfo = $"[DeleteSingle] ==> 删除数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return rcount > 0;
    }

    public async Task<int> SqliteDeleteBulkAsync<T>(List<Guid> ids) where T : BaseEntity
    {
        _sw.Start();
        int rcount = await _freeSql.Delete<T>(ids.ToArray()).ExecuteAffrowsAsync();
        _sw.Stop();
        TimeSpan ts = _sw.Elapsed;

        // 记录操作方法执行的时间
        var actionExecInfo = new ActionExecInfo
        {
            ActionName = "DeleteBulk",
            ExecTime = ts,
            Database = "sqlite"
        };

        // 记录日志
        var appLogs = new AppLogs
        {
            Label = "DeleteBulk",
            ItemCount = rcount,
            OperationInfo = $"[DeleteBulk] ==> 删除数据:{rcount}条,耗时:{ts.TotalMilliseconds}ms."
        };
        await LiteDbWraiteInfoAsync(actionExecInfo, appLogs);

        return rcount;
    }
    #endregion
}

4、程序入口类

  • Program.cs
using Serilog;
using WebAppDbTest.Services;

var builder = WebApplication.CreateBuilder(args);

//const string OUTPUT_TEMPLATE = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} <{ThreadId}> [{Level:u3}] {Message:lj}{NewLine}{Exception}";
const string OUTPUT_TEMPLATE = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
char b = Path.DirectorySeparatorChar; // 符号 

// creates custom collection `applog`
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Enrich.FromLogContext()
    .CreateLogger();

#region Host
builder.Host.ConfigureAppConfiguration((context, config) => {
    string configPath = $"{context.HostingEnvironment.ContentRootPath}{b}AppData{b}Configuration";
    config.SetBasePath(configPath)
      .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
      .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true)
      .AddEnvironmentVariables();
}).UseSerilog((context, logger) => {
    string liteDbPath = Path.Combine(context.HostingEnvironment.ContentRootPath, $"AppData{b}DataBase{b}LiteDbLogs.db");
    logger.WriteTo.LiteDB(liteDbPath, logCollectionName: "applog");
    logger.WriteTo.Console(outputTemplate: OUTPUT_TEMPLATE);
});
// .UseSerilog(Log.Logger, dispose: true);
#endregion

#region Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// 注册 AppLogsServices
builder.Services.AddScoped<IAppLogsServices, AppLogsServices>();
// 注册 WeatherForecastServices
builder.Services.AddScoped<IWeatherForecastServices, WeatherForecastServices>();

// 注入 Sqlite 类型的 IFreeSql 
//string sqlitePath = $"AppData{b}DataBase{b}SQLiteTest.db";
string sqlitePath = builder.Configuration.GetSection("DbConfig:1:DbFilePath").Value;
string connStr = $"Data Source={Path.Combine(builder.Environment.ContentRootPath, sqlitePath)};Version=3;Pooling=False;Max Pool Size=100";
// Log.Logger.Information(connStr);

IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(dataType: FreeSql.DataType.Sqlite, connectionString: connStr)
    .UseAutoSyncStructure(false) //自动同步实体结构【开发环境必备】,FreeSql不会扫描程序集,只有CRUD时才会生成表。
    //.UseMonitorCommand(cmd => Console.Write(cmd.CommandText)) 
    .UseMonitorCommand(cmd => Log.Logger.Information(cmd.CommandText))
    .Build(); //请务必定义成 Singleton 单例模式
builder.Services.AddSingleton(fsql); 
#endregion

var app = builder.Build();

#region Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();
app.MapControllers(); 
#endregion

app.Run();

3.2、json 配置文件说明

  • appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "DbConfig": [
    {
      "DbType": "LiteDB",
      "DbFilePath": "AppData\\DataBase\\LiteDbTest.db"
    },
    {
      "DbType": "SQLite",
      "DbFilePath": "AppData\\DataBase\\SqliteTest.db"
    }
  ]
}

相关文件代码此处就不再详细说明,感兴趣的可自行查看项目地址:

  • WebAppDbTesthttps://gitee.com/dolayout/sample/tree/master/code/Sample.WebAppDbTest

4、项目运行预览

  • 启动 WebAppDbTestswagger 页面显示如下:

webapp

  • LiteDB & Sqlite 对应的 CRUD 方法:

webapp-dbtest-crud

数据库 .db 文件准备

1、创建 SQLite 数据库

请访问 SQLite 下载页面,从 Windows 区下载预编译的二进制文件。

1.1、在 Windows 上安装 SQLite

  • SQLite 下载,https://www.sqlite.org/download.html
    sqlite-download

此处我是 Windows 11 x64 环境,下载文件分别如下:

  • sqlite-dll-win-x64-3440200.zip
  • sqlite-tools-win-x64-3440200.zip

把下载文件拷贝到 D 盘并解压文件,如下所示:

sqlite

文件夹默认文件说明:

  • sqlite-dll-win-x64-3440200 文件夹默认包含:sqlite3.defsqlite3.dll 文件;
  • sqlite-tools-win-x64-3440200 文件夹默认包含:sqldiff.exesqlite3.exesqlite3_analyzer.exe 文件;

可以把 D:\sqlite\sqlite-tools-win-x64-3440200 添加到 PATH 环境变量,最后在命令提示符下,使用 sqlite3 命令,此处我就不添加环境变量了,直接双击 sqlite.exe 文件将显示如下结果:

sqlite-tools-win-x64

1.2、创建 SQLite 数据库

依据终端提示信息,输入命令创建数据库 SQLiteTest.db 文件,执行如下:

sqlite> .open SQLiteTest.db

查看 sqlite 更多命令帮助信息:

SQLite version 3.44.2 2023-11-24 11:41:44 (UTF-16 console I/O)
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open SQLiteTest.db
sqlite> PRAGMA key = '123456';
sqlite> .help
.archive ...             Manage SQL archives
.auth ON|OFF             Show authorizer callbacks
.backup ?DB? FILE        Backup DB (default "main") to FILE
.bail on|off             Stop after hitting an error.  Default OFF
.cd DIRECTORY            Change the working directory to DIRECTORY
.changes on|off          Show number of rows changed by SQL
.check GLOB              Fail if output since .testcase does not match
.clone NEWDB             Clone data into NEWDB from the existing database
.connection [close] [#]  Open or close an auxiliary database connection
.crnl on|off             Translate \n to \r\n.  Default ON
.databases               List names and files of attached databases
.dbconfig ?op? ?val?     List or change sqlite3_db_config() options
.dbinfo ?DB?             Show status information about the database
.dump ?OBJECTS?          Render database content as SQL
.echo on|off             Turn command echo on or off
.eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN
.excel                   Display the output of next command in spreadsheet
.exit ?CODE?             Exit this program with return-code CODE
.expert                  EXPERIMENTAL. Suggest indexes for queries
.explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto
.filectrl CMD ...        Run various sqlite3_file_control() operations
.fullschema ?--indent?   Show schema and the content of sqlite_stat tables
.headers on|off          Turn display of headers on or off
.help ?-all? ?PATTERN?   Show help text for PATTERN
.import FILE TABLE       Import data from FILE into TABLE
.indexes ?TABLE?         Show names of indexes
.limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT
.lint OPTIONS            Report potential schema issues.
.load FILE ?ENTRY?       Load an extension library
.log FILE|on|off         Turn logging on or off.  FILE can be stderr/stdout
.mode MODE ?OPTIONS?     Set output mode
.nonce STRING            Suspend safe mode for one command if nonce matches
.nullvalue STRING        Use STRING in place of NULL values
.once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE
.open ?OPTIONS? ?FILE?   Close existing database and reopen FILE
.output ?FILE?           Send output to FILE or stdout if FILE is omitted
.parameter CMD ...       Manage SQL parameter bindings
.print STRING...         Print literal STRING
.progress N              Invoke progress handler after every N opcodes
.prompt MAIN CONTINUE    Replace the standard prompts
.quit                    Stop interpreting input stream, exit if primary.
.read FILE               Read input from FILE or command output
.recover                 Recover as much data as possible from corrupt db.
.restore ?DB? FILE       Restore content of DB (default "main") from FILE
.save ?OPTIONS? FILE     Write database to FILE (an alias for .backup ...)
.scanstats on|off|est    Turn sqlite3_stmt_scanstatus() metrics on or off
.schema ?PATTERN?        Show the CREATE statements matching PATTERN
.separator COL ?ROW?     Change the column and row separators
.session ?NAME? CMD ...  Create or control sessions
.sha3sum ...             Compute a SHA3 hash of database content
.shell CMD ARGS...       Run CMD ARGS... in a system shell
.show                    Show the current values for various settings
.stats ?ARG?             Show stats or turn stats on or off
.system CMD ARGS...      Run CMD ARGS... in a system shell
.tables ?TABLE?          List names of tables matching LIKE pattern TABLE
.timeout MS              Try opening locked tables for MS milliseconds
.timer on|off            Turn SQL timer on or off
.trace ?OPTIONS?         Output each SQL statement as it is run
.version                 Show source, library and compiler versions
.vfsinfo ?AUX?           Information about the top-level VFS
.vfslist                 List all available VFSes
.vfsname ?AUX?           Print the name of the VFS stack
.width NUM1 NUM2 ...     Set minimum column widths for columnar output
sqlite>

此时在当前目录下,SQLite 的数据库文件 SQLiteTest.db 文件就创建好了。

接下来使用 dbeaver-ce 工具连接数据库文件测试:

连接测试

  • sqlite 数据表脚本:
-- WeatherForecast definition

CREATE TABLE "WeatherForecast" (  
  "Id" CHARACTER(36) NOT NULL, 
  "Date" TEXT NOT NULL, 
  "TemperatureC" INTEGER NOT NULL, 
  "TemperatureF" INTEGER NOT NULL, 
  "Summary" NVARCHAR(255), 
  PRIMARY KEY ("Id")
);

2、创建 LiteDB 数据库

2.1、LiteDB.Shell

LiteDB 项目包含一个简单的控制台应用程序 (LiteDB.Shell.exe),可用于查看、更新以及测试你的数据,在处理你的数据库时非常有用。

  • LiteDB.Shell 项目地址,https://github.com/mustakimali/LiteDB.Shell.NetCore

2.2、创建 LiteDB 数据库

使用 LiteDB.Shell 创建数据库,执行如下命令:

> open <filename>|<connectionString>
    Open/Crete a new database

基本 Shell 命令,尝试使用 help full 执行所有命令:

Basic Shell Commands - try `help full` for all commands
=======================================================
> open <filename>|<connectionString>
    Open/Crete a new database

> show collections
    List all collections inside database

> db.<collection>.insert <jsonDoc>
    Insert a new document into collection

> db.<collection>.update <jsonDoc>
    Update a document inside collection

> db.<collection>.delete <filter>
    Delete documents using a filter clausule (see find)

> db.<collection>.find <filter> [skip N][limit N]
    Show filtered documents based on index search

> db.<collection>.count <filter>
    Show count rows according query filter

> db.<collection>.ensureIndex <field> [true|{options}]
    Create a new index document field. For unique key, use true

> db.<collection>.indexes
    List all indexes in this collection

<filter> = <field> [=|>|>=|<|<=|!=|like|between] <jsonValue>
    Filter query syntax

<filter> = (<filter> [and|or] <filter> [and|or] ...)
    Multi queries syntax

Try:
 > db.customers.insert { _id:1, name:"John Doe", age: 37 }
 > db.customers.ensureIndex name
 > db.customers.find name like "John"
 > db.customers.find name like "John" and _id between [0, 100] limit 10

说明:litedb 数据库和数据集无需创建,当不存在时执行 crud 代码会自动创建。

好了先到这里,我们就把测试项目准备好了,关于接口测试性能对比,下篇再续,敬请观看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1303517.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

赛宁网安多领域亮相第三届网络空间内生安全发展大会

2023年12月8日&#xff0c;第三届网络空间内生安全发展大会在宁开幕。两院院士、杰出专家学者和知名企业家相聚南京&#xff0c;围绕数字经济新生态、网络安全新范式进行广泛研讨&#xff0c;为筑牢数字安全底座贡献智慧和力量。 大会围绕“一会、一赛、一展”举办了丰富多彩的…

【华为数据之道学习笔记】3-11元数据管理

1. 产生元数据 &#xff08;1&#xff09;明确业务元数据、技术元数据和操作元数据之间的关系&#xff0c;定义华为公司元数据模型。 &#xff08;2&#xff09;针对找数据及获取数据难的痛点&#xff0c;明确业务元数据、技术元数据、操作元数据的设计原则。 1&#xff09;业务…

虚拟化逻辑架构:KVM虚拟机通过OVS端口组实现网络连接

目录 一、实验 1.CentOS 7 安装 OpenVSwitch(构建RPM安装包&#xff09; 2.KVM虚拟机通过OVS端口组实现网络连接 二、问题 1.安装openvswitch-2.5.10报错 2.virt-install未找到命令 3.如何删除自定义网络 一、实验 1.CentOS 7 安装 OpenVSwitch(构建RPM安装包&#xff…

蓝桥杯周赛 第 1 场 强者挑战赛 6. 小球碰撞【算法赛】(思维题/最长上升子序列LIS)

题目 https://www.lanqiao.cn/problems/9494/learning/?contest_id153 思路来源 Aging代码 题解 二分时间t&#xff0c;第i个小球对应一个起点pi、终点pit*vi的区间&#xff0c;问题转化为&#xff0c; 选最多的区间&#xff0c;使得不存在区间包含&#xff08;即li<l…

不同路径dp问题

1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 从上往下填写每一行 每一行从左往右 5.返回值 dp[m][n] ------------------------------------------------------------------------------------------------------------------------------ 1.状态表示 2.状态转移方程 3…

每日一题,头歌平台c语言题目

任务描述 题目描述:输入一个字符串&#xff0c;输出反序后的字符串。 相关知识&#xff08;略&#xff09; 编程要求 请仔细阅读右侧代码&#xff0c;结合相关知识&#xff0c;在Begin-End区域内进行代码补充。 输入 一行字符 输出 逆序后的字符串 测试说明 样例输入&…

DevOps:自动化、持续交付和持续集成实践

DevOps&#xff1a;自动化、持续交付和持续集成实践 一、DevOps 简介1.1 DevOps 模式定义1.2 DevOps 的工作原理1.3 DevOps 的优势1.4 DevOps 的意义1.5 DevOps 最佳实践 二、持续集成简介2.1 持续集成的意义2.2 持续集成的工作原理2.3 持续集成的优势 三、持续交付简介3.1 持续…

解决夜神模拟器与Android studio自动断开的问题

原因&#xff1a;夜神模拟器的adb版本和Android sdk的adb版本不一致 解决办法&#xff1a; 1.找到android的sdk &#xff08;1&#xff09;File--->Project Structure (2)SDK Location:记下sdk的位置 2.找到sdk中的adb文件 SDK-->platform-tools-->adb.exe 3.复制…

单片机(STM32,GD32,NXP等)中BootLoader的严谨实现详解

Bootloader(引导加载程序)的主要任务是引导加载并运行应用程序&#xff0c;我们的软件升级逻辑也一般在BootLoader中实现。本文将详细介绍BootLoader在单片机中的实现&#xff0c;包括STM32、GD32、NXP Kinetis等等的所有单片机&#xff0c;因为无论是什么样的芯片&#xff0c;…

大模型应用_ChatGPT-Next-Web

1 用后感 这个工具&#xff0c;我也是用了好长时间&#xff0c;就是感觉如果不点亮一颗星&#xff0c;自己就不是人了的那种。 一开始在国内用 ChatGPT 非常麻烦&#xff0c;就买了一个套壳的服务&#xff0c;他使用的界面就是 ChatGPT-Next-Web&#xff0c;我和朋友们都觉得这…

深度学习 Day13——P2彩色图片分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 文章目录 前言1 我的环境2 代码实现与执行结果2.1 前期准备2.1.1 引入库2.1.2 设置GPU&#xff08;如果设备上支持GPU就使用GPU,否则使用C…

基于JavaWeb+BS架构+SpringBoot+Vue图书个性化推荐系统的设计和实现

基于JavaWebSpringBootVue图书个性化推荐系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图 源码获取入口 Lun文目录 目 录 摘 要 I 1 绪论 1 1.1研究背景 1 1.2研究现状 1 1.3研究内容 2 2 系统关键技术 3 2.1 Spring Boot框架 3 2.2 JAVA技术 3 2.3 MY…

实现安装“自由化”!在Windows 11中如何绕过“您尝试安装的应用程序未通过微软验证”

这篇文章描述了如果你不能安装应用程序,而是当你在Windows 11中看到消息“您尝试安装的应用程序未通过微软验证”时该怎么办。完成这些步骤将取消你安装的应用程序必须经过Microsoft验证的要求。 使用设置应用程序 “设置”应用程序提供了绕过此警告消息的最简单方法,以便你…

051:vue项目webpack打包后查看各个文件大小

第050个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

centos7部署docker

文章目录 &#xff08;1&#xff09;安装前准备&#xff08;2&#xff09;卸载旧版Docker&#xff08;3&#xff09;安装docker&#xff08;4&#xff09;配置镜像加速 &#xff08;1&#xff09;安装前准备 在开始安装之前&#xff0c;首先需要检查内核版本。使用 uname -r 命…

RPG:一种面向Rust库的模糊测试目标自动生成技术(ICSE‘24)

- 这是我们在ICSE’24的论文RPG: Rust Library Fuzzing with Pool-based Fuzz TargetGeneration and Generic Support [1] 的科普版本。- 插播一条广告&#xff1a;西安电子科技大学广州研究院 ICTT(GZ) 实验室长期接收硕士、博士、博士后、教师岗位申请&#xff0c;欢迎勤奋、…

gitlab动态流水线

文章目录 1. 说明2. 官方样例2.1 在作业中生成配置文件&#xff0c;保存为产物2.2 将触发器作业配置为在生成配置文件的作业之后运行。 3. 实战应用3.1 背景介绍3.2 项目介绍3.3 公共项目配置3.4 测试项目配置3.5 测试 4. 总结 1. 说明 顾名思义&#xff0c;动态流水线就是一种…

青少年CTF-Misc(持续更新中)

FLAG&#xff1a;当觉得自己很菜的时候&#xff0c;就静下心来学习 专研方向:Web安全&#xff0c;CTF 每日emo&#xff1a;听一千遍反方向的钟&#xff0c;我们能回到过去吗&#xff1f; 1.StegoTXT&#xff1a; 解压缩文件。发现字母中存在覆盖。使用0宽隐写在线解密得到flag…

音乐制作工具 Ableton Live 12中文最新 for Mac

Ableton Live 12 Mac具有直观的界面和强大的功能&#xff0c;使得音乐制作变得更加简单和高效。它支持实时录制、编辑和混音&#xff0c;用户可以在创作过程中随时进行修改和调整。此外&#xff0c;该软件还提供了各种音频效果、虚拟乐器和采样器&#xff0c;使用户可以创建出更…

Swagger快速上手

快速开始&#xff1a; 导入maven包 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version> </dependency><dependency><groupId>io.springfox<…