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

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 代码会自动创建。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

区块链实验室(31) - 交叉编译Ethereum的客户端Geth

编译Geth到X86架构平台 下载Geth源码&#xff0c;直接编译Geth源码&#xff0c;见下图。用file命令观察编译后的文件&#xff0c;架构是x86-64。 编译Geth到Arm64架构平台 直接用命令行编译&#xff0c;同时指定期望的架构为Arm64。编译脚本如下所示。 CGO_ENABLED0 GOOSlin…

VSCode使用Remote-SSH连接服务器时报错:无法与“***”建立连接: XHR failed.

关于VSCode的报错问题&#xff1a;无法与“***”建立连接: XHR failed 问题描述问题理解解决方法手动在本地下载安装包&#xff0c;然后手动传到服务器端 问题描述 是的&#xff0c;我又踩坑了&#xff0c;而且这个弄了好久&#xff0c;也重新装了VSCode软件&#xff0c;好像结…

arm-linux设备fsck命令移植

arm-linux设备fsck命令移植 文章目录 **arm-linux设备fsck命令移植**1、下载e2fsprogs-源码2、解压3、进入源码目录4、配置编译环境&#xff1a;使用以下命令配置交叉编译环境5、测试 1、下载e2fsprogs-源码 首先要确定自己的文件系统格式&#xff0c;IG2000的文件系统是ext4&…

防止域名被攻击或盗刷产生突发高带宽,阿里云国际如何设置带宽上限值?

为防止域名被攻击或盗刷产生突发高带宽&#xff0c;导致产生高额账单&#xff0c;可通过配置带宽封顶&#xff0c;控制用户访问该域名的带宽上限值&#xff0c;减少因突发流量导致的损失。 功能介绍 带宽封顶&#xff0c;即通过设置带宽上限&#xff0c;来控制带宽用量。当指…

博士毕业需要发表几篇cssci论文

大家好&#xff0c;今天来聊聊博士毕业需要发表几篇cssci论文&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 博士毕业需要发表几篇CSSCI论文 背景介绍 CSSCI即“中文社会科学引文索引”&#xff0c;被…

从生态的角度看容器和虚拟化的区别

「执笔人」品高股份 cloud native 资深架构师&#xff1a;继承 概述 容器技术和虚拟机技术都是近些年计算平台的主要的计算虚拟化技术&#xff0c;各家云厂商都提供虚拟机和容器服务&#xff0c;关于容器和虚拟化的区别&#xff0c;业界已经有很多共识了&#xff0c;例如&…

【NR技术】NR NG-RAN整体架构 -功能划分(三)

1 概述 NG-RAN节点包括: gNB&#xff0c;向终端提供NR用户平面和控制平面协议终端;ng-eNB&#xff0c;向终端提供E-UTRA用户平面和控制平面的协议终端。gNB和ng- eNB通过Xn接口相互连接。gnb和NG- eNB也通过NG接口连接到5GC&#xff0c;更具体地说&#xff0c;通过NG-C接口连…

c语言插入排序及希尔排序详解

目录 前言&#xff1a; 插入排序&#xff1a; 希尔排序&#xff1a; 前言&#xff1a; 排序在我们生活中无处不在&#xff0c;比如学生成就排名&#xff0c;商品价格排名等等&#xff0c;所以排序在数据结构的学习中尤为重要&#xff0c;今天就为大家介绍两个经典的排序算法&…

1.9 实战:Postman全局变量

上一小节我们学习了环境管理器以及环境变量&#xff0c;这一小节我们再看另一种变量&#xff0c;全局变量。我们知道环境变量只能在当前环境下使用&#xff0c;如果换了一个环境&#xff0c;那之前的变量就没法使用了。比如我们建了两个环境&#xff0c;一个生产环境&#xff0…

开箱即用的C++决策树简单实现

一个数据结构期末作业&#xff08;有兴趣用的话可以高抬贵手star下⭐~&#xff09;GitHub - mcxiaoxiao/c-Decision-tree: 决策树c简单实现 &#x1f333; c-Decision-tree 附大作业/课设参考文档.doc &#x1f333; c-Decision-tree Introduction &#x1f64c; c-Decision…

PPT插件-好用的插件-超级对齐-大珩助手

超级对齐 包含对齐幻灯、对齐对象、对齐文本三个层级&#xff0c;可共用水平分布、垂直分布、交换位置、统一尺寸、垂直居中、水平居中、绝对居中、靠左对齐、靠右对齐、靠上对齐、靠下对齐 可配合图形缩放使用 可配合文本打散使用 可配合素材库中的一键替换使用 选中场景中的…

新增模板中心和系统设置模块,支持飞书平台对接,DataEase开源数据可视化分析平台v2.1.0发布

这一版本的功能升级包括&#xff1a;新增模板中心&#xff0c;用户可以通过模板中心的模板快速创建仪表板和数据大屏&#xff1b;新增“系统设置”功能模块&#xff0c;该模块包含系统参数、认证设置、嵌入式管理、平台对接四个子模块。在“系统参数”子模块中&#xff0c;用户…

uniapp - 简单版本自定义tab栏切换

tab切换是APP开发最常见的功能之一&#xff0c;uniapp中提供了多种形式的tab组件供我们使用。对于简单的页面而言&#xff0c;使用tabbar组件非常方便快捷&#xff0c;可以快速实现底部导航栏的效果。对于比较复杂的页面&#xff0c;我们可以使用tab组件自由定义样式和内容 目录…

C# OpenCvSharp DNN 部署FastestDet

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN 部署FastestDet 效果 模型信息 Inputs ------------------------- name&#xff1a;input.1 tensor&#xff1a;Float[1, 3, 512, 512] --------------------------------------------------------------- Outpu…

大数据技术10:Flink从入门到精通

导语&#xff1a;前期入门Flink时&#xff0c;可以直接编写通过idea编写Flink程序&#xff0c;然后直接运行main方法&#xff0c;无需搭建环境。我碰到许多初次接触Flink的同学&#xff0c;被各种环境搭建、提交作业、复杂概念给劝退了。前期最好的入门方式就是直接上手写代码&…

Java SPI机制学习

最近阅读源码时看到了SPI加载类的方式 这里学习一下 SPI Service Provider Interface 服务提供接口 API和SPI区别 API是接口和实现类均有服务提供方负责 服务调用方更多是一种主动调用 只是调用SDK来实现某个功能&#xff1b;接口的归属权在于服务提供方 SPI是接口在调用方…

N体问题-MATLAB 中的数值模拟

一、说明 万有引力是宇宙普适真理&#xff0c;当计算两个物体的引力、质量、距离的关系是经典万有引力物理定律&#xff0c;然而面向复杂问题&#xff0c;比如出现三个以上物体的相互作用&#xff0c;随时间的运动力学&#xff0c;这种数学模型将是更高级的思维方法。本文将阐述…

jsp文件引用的css修改后刷新不生效问题

问题 在对 JavaWeb 项目修改的过程中&#xff0c;发现修改了 jsp 文件引入的 css 文件的代码后页面的样式没有更新的问题。 原因 导致这个问题的原因可能是因为浏览器缓存的问题。 解决方法 下面介绍两种解决方法&#xff0c;供大家参考&#xff1a; 1、给 link 标签的 c…

软件测试(接口测试业务场景测试)

软件测试 手动测试 测试用例8大要素 编号用例名称&#xff08;标题&#xff09;模块优先级预制条件测试数据操作步骤预期结果 接口测试&#xff08;模拟http请求&#xff09; 接口用例设计 防止漏测方便分配工具&#xff0c;评估工作量和时间接口测试测试点 功能 单接口业…

css的Grid布局

1.简单布局 .grid { display: grid; grid-template-columns: 1fr 2fr 1fr; 布局样式 column-gap: 24px; 列间距 row-gap: 24px; 行间距 } 2.排列布局 center垂直方向居中对其 end靠下对齐 3.水平方向对齐 center居中 end靠右对齐 space-between两段对齐 4.对…