如何使用 FreeSql 无缝接替 EF Core,并实现数据表的 CRUD 操作
- 项目说明
- DB & 数据表结构
- DB & 数据表创建
- 数据表 User 实体模型创建
- 使用 EF Core 实现 User 表新增用户信息
- 添加 EF Core 相关的 nuget 包
- 编写 EF Core 操作 User 表的 CRUD 代码
- FreeSql 使用 DbContext 接替 EF Core
- 单例对象构造器 SingletonConstructor
- 使用 SingletonConstructor 构建 IFreeSql
- 反射解析 DatabaseFacade
- EF Core 中 DbContext 官方定义
- EF Core 中 DatabaseFacade 官方定义
- 反射解析对象 DatabaseFacade
- 测试 orm 两种模式下的操作
- 添加 User 用户信息
- FreeSql 查询 User 用户信息
- 总结
项目说明
- 实现目标:使用
FreeSql
无缝接替EF Core
,并实现数据表的CRUD
操作;
接下来我们先回顾下 EF Core
中如何实现数据表的 CRUD
操作,在操作之前我们先把数据库和相关表结构准备好。
DB & 数据表结构
- 数据库实例名称:
Test
- 数据表结构:
User
此处为了方便演示,创建一个简单的 User
表,数据结构如下:
字段 | 说明 |
---|---|
Id | 主键,用户ID |
Name | 用户姓名 |
DB & 数据表创建
创建数据库实例 Test
和 User
表,执行如下 sql
脚本:
USE master;
GO -- 创建 Test
CREATE DATABASE Test
ON
( NAME = Test_dat, FILENAME = 'D:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\testdat.mdf', SIZE = 10, MAXSIZE = 50, FILEGROWTH = 5 )
LOG ON
( NAME = Test_log, FILENAME = 'D:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\testlog.ldf', SIZE = 5MB, MAXSIZE = 25MB, FILEGROWTH = 5MB );
GOCREATE TABLE Test.dbo.[User] (Id varchar(36) NOT NULL primary key,Name varchar(20) NOT NULL
);
EXEC Test.sys.sp_addextendedproperty 'MS_Description', N'用户信息', 'schema', N'dbo', 'table', N'User';
GO
执行上面 sql
脚本创建数据库失败时,可以先执行如下 sql
脚本,然后在继续执行上面步骤。
-- 关闭数据库连接并删除数据库
ALTER DATABASE Test SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE Test;
除了上面常规的 mssql
配置,开发环境为了方便快捷,还可以使用 Docker
运行 mssql
镜像(mcr.microsoft.com/mssql/server
),此处我是配置的 Docker
环境(这里不做详细介绍,感兴趣的小伙伴自行查看资料):
到这里我们就把基本的开发环境准备好了,接下来就是新建项目相关环节的操作。
数据表 User 实体模型创建
依据上面的 User
表结构,在(新建)项目 Jeff.Mes.EntityFrameworkCore
中添加 C#
模型类:
using System.ComponentModel.DataAnnotations.Schema;namespace Jeff.Mes.EntityFrameworkCore;/// <summary>
/// 用户信息表
/// </summary>
[Table("User")]
public class User
{/// <summary>/// 主键,用户ID /// </summary>public string Id { get; set; }/// <summary>/// 用户姓名/// </summary>public string Name { get; set; }
}
使用 EF Core 实现 User 表新增用户信息
上面我们准备好 Test 数据库、User 表和 C# 实体模型结构
后,接下来准备编写 EF Core
相应的操作代码。
添加 EF Core 相关的 nuget 包
说明:此处环境使用的 SqlServer 数据库,其他类型的数据库需添加相应的 nuget 包。
接着我们继续在上面新建的项目【Jeff.Mes.EntityFrameworkCore
】中改造,添加 nuget
包:
Microsoft.EntityFrameworkCore.SqlServer
NuGet\Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 6.0.14
编写 EF Core 操作 User 表的 CRUD 代码
项目【Jeff.Mes.EntityFrameworkCore
】中添加 FreeSql
的 Nuget
包:
FreeSql.Provider.SqlServer
NuGet\Install-Package FreeSql.Provider.SqlServer -Version 3.2.687
在项目【Jeff.Mes.EntityFrameworkCore
】中新建 TestContext
类,继承 DbContext
,实现如下:
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;namespace Jeff.Mes.EntityFrameworkCore;/// <summary>
/// Test 数据库上下文对象
/// </summary>
public class TestContext : DbContext
{public TestContext() { }/// <summary>/// 用户信息表/// </summary>public virtual DbSet<User> User { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){if (!optionsBuilder.IsConfigured){// db 连接字符串,其中符号 “***” 代表数据库访问密码string dbConnString = "Data Source=.;Initial Catalog=Test;Persist Security Info=True;User ID=sa;Password=***";optionsBuilder.UseSqlServer(dbConnString);}}/// <summary>/// 【EF Core 模式】添加用户测试/// </summary>/// <returns></returns>public async Task<int> AddUserAsync(){using (var dbContext = new TestContext()){var guid = Guid.NewGuid().ToString();var user = new User{Id = guid,Name = $"efcore-{guid.Substring(0, 5)}"};dbContext.Add(user);return await dbContext.SaveChangesAsync();}}// 注意对比下面的,【FreeSql 模式】添加用户测试
}
上面操作 User
表的 CRUD
代码,为了简化此处只写 AddUserAsync
添加操作,其他代码就不在详细介绍。
FreeSql 使用 DbContext 接替 EF Core
在 Entity Framework(简称 EF)
中创建模型后,应用程序所交互的主要类是 System.Data.Entity.DbContext
(通常称为 上下文类
)。默认情况下,上下文管理与数据库的连接。
单例对象构造器 SingletonConstructor
单例对象构造器 SingletonConstructor
代码如下:
namespace Jeff.Mes.Common;/// <summary>
/// 单例对象构造器
/// </summary>
/// <typeparam name="T"></typeparam>
public class SingletonConstructor<T> where T : class, new()
{private static T? _Instance;private readonly static object _lockObj = new();/// <summary>/// 获取单例对象的实例/// </summary>/// <returns></returns>public static T GetInstance(){if (_Instance != null) return _Instance;lock (_lockObj){if (_Instance == null){var item = System.Activator.CreateInstance<T>();System.Threading.Interlocked.Exchange(ref _Instance, item);}}return _Instance;}
}
使用 SingletonConstructor 构建 IFreeSql
新建 FreeSqlHelper.cs
文件,使 FreeSqlHelper
类继承自 SingletonConstructor
单例构造器,代码示例如下:
using System.Collections.Concurrent;
using FreeSql;
using Jeff.Mes.Common;namespace Jeff.Mes.DbHelper;/// <summary>
/// 【Singleton 单例模式】构建 freesql 对象
/// </summary>
public sealed class FreeSqlHelper : SingletonConstructor<FreeSqlHelper>
{//连接字符串作为 key,存储构建的 IFreeSql 对象的字典集合private readonly static ConcurrentDictionary<string, IFreeSql> _FreeDic = new();#region 构建 freesql 对象public IFreeSql? FreeBuilder(string dbType, string connStr){if (string.IsNullOrWhiteSpace(dbType) || string.IsNullOrWhiteSpace(connStr)){return default;}bool isOk = _FreeDic.TryGetValue(connStr, out IFreeSql? fsql);if (isOk){return fsql;}DataType dataType;string myDbType = dbType.Contains('.') ? dbType.Substring(dbType.LastIndexOf('.') + 1) : dbType;switch (myDbType.ToLower()){case "mysql":dataType = DataType.MySql;break;default:dataType = DataType.SqlServer;break;}return FreeBuilder(dataType, connStr);}public IFreeSql? FreeBuilder(DataType dbType, string connStr){if (string.IsNullOrWhiteSpace(connStr)){return default;}/*bool hasKey = _FreeDic.ContainsKey(connStr);if (hasKey){return _FreeDic[connStr];}*/bool isOk = _FreeDic.TryGetValue(connStr, out IFreeSql? fsql);if (isOk){return fsql;}fsql = new FreeSqlBuilder().UseConnectionString(dbType, connStr).UseAutoSyncStructure(false) //自动同步实体结构到数据库.Build(); //请务必定义成 Singleton 单例模式 bool isAdd = _FreeDic.TryAdd(connStr, fsql);if (isAdd){return fsql;}else{fsql.Dispose();return _FreeDic[connStr];}}public (bool isOk, IFreeSql? fsql) GetFreeSql(DataType dbType, string connStr){bool isOk = _FreeDic.TryGetValue(connStr, out IFreeSql? fsql);if (!isOk){fsql = FreeBuilder(dbType, connStr);isOk = fsql != null;}return (isOk, fsql ?? default);}#endregion
}
使用 FreeSqlHelper
对象构建 IFreeSql
,如下代码:
IFreeSql fsql = FreeSqlHelper.GetInstance().FreeBuilder(dbType, connStr);
上面环节我们就准备好了 IFreeSql
对象的构建,万事俱备,只欠东风(下面我们解析解析 DbContext
获取有效信息);
反射解析 DatabaseFacade
在反射解析 DbContext
对象之前,我们先回顾下相关概念定义和类库基本信息。
EF Core 中 DbContext 官方定义
Microsoft.EntityFrameworkCore.DbContext
上面我简单的介绍了 DbContext
是 EF Core
的 数据库连接对象,接下来我们看下官方定义:
- 命名空间:
Microsoft.EntityFrameworkCore
- 程序集:
Microsoft.EntityFrameworkCore.dll
- Nuget 包:
Microsoft.EntityFrameworkCore v7.0.0
DbContext
实例表示与数据库的会话,可用于查询和保存实体的实例。 DbContext
是工作单元和存储库模式的组合。
public class DbContext : IAsyncDisposable,
IDisposable, Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<IServiceProvider>,
Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies,
Microsoft.EntityFrameworkCore.Internal.IDbContextPoolable,
Microsoft.EntityFrameworkCore.Internal.IDbSetCache
关于 DbContext 更多信息,请查看 =》https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.entityframeworkcore.dbcontext?view=efcore-7.0
EF Core 中 DatabaseFacade 官方定义
Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade
DatabaseFacade
类定义如下:
- 命名空间:
Microsoft.EntityFrameworkCore.Infrastructure
- 程序集:
Microsoft.EntityFrameworkCore.dll
- Nuget 包:
Microsoft.EntityFrameworkCore v7.0.0
提供对上下文的数据库相关信息和操作的访问。 此类的实例通常是从 Database
中获取的,它不是在应用程序代码中直接构造的。
public class DatabaseFacade :
Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<IServiceProvider>,
Microsoft.EntityFrameworkCore.Infrastructure.IResettableService,
Microsoft.EntityFrameworkCore.Storage.IDatabaseFacadeDependenciesAccessor
关于 DatabaseFacade 更多信息,请查看 =》https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.entityframeworkcore.infrastructure.databasefacade?view=efcore-7.0
反射解析对象 DatabaseFacade
Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade
添加反射解析 DatabaseFacade
对象的代码:
/// <summary>
/// 根据对象实例和属性名称获得属性值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="property"></param>
/// <returns></returns>
public static T? GetProperty<T>(this object obj, string property)
{var result = default(T);try{var t = obj.GetType();var propertyObj = t.GetProperty(property)?.GetValue(obj, null);result = (T?)propertyObj.ChangeType(typeof(T));return result;}catch(Exception ex){throw ex;}
}/// <summary>
/// 类型转换
/// </summary>
/// <param name="value"></param>
/// <param name="type"></param>
/// <returns></returns>
public static object? ChangeType(this object? value, Type type)
{if (value == null && type.IsGenericType) return Activator.CreateInstance(type);if (value == null) return null;if (type == value.GetType()) return value;if (type.IsEnum){if (value is string valEnum) return Enum.Parse(type, valEnum);else return Enum.ToObject(type, value);}if (!type.IsInterface && type.IsGenericType){Type innerType = type.GetGenericArguments()[0];object? innerValue = ChangeType(value, innerType);return Activator.CreateInstance(type, new object?[] { innerValue });}if (value is string valGuid && type == typeof(Guid)) return new Guid(valGuid);if (value is string valVersion && type == typeof(Version)) return new Version(valVersion);if (value is Guid && type == typeof(string)) return value.ToString();if (value is not IConvertible) return value;if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>))){var underlyingType = Nullable.GetUnderlyingType(type);type = underlyingType ?? type;} // end ifreturn Convert.ChangeType(value, type);
}
继续在 TestContext.cs
类中添加如下代码:
/// <summary>
/// 【FreeSql 模式】添加用户测试
/// </summary>
/// <returns></returns>
public async Task<int> FreeSqlAddUserAsync()
{using (var dbContext = new TestContext()){var (myDbContext, fsql) = GetDbContext(dbContext);var guid = Guid.NewGuid().ToString();var user = new User{Id = guid,Name = $"fsql-{guid.Substring(0, 5)}"};// fsql.Insert(user).AsTable("User").ExecuteAffrowsAsync();return await fsql.Insert(user).ExecuteAffrowsAsync();}
}/// <summary>
/// 反射获取信息,并构建 FreeSql
/// </summary>
/// <param name="dbContext"></param>
/// <returns></returns>
public (DbContext dbContext, IFreeSql fsql) GetDbContext(DbContext dbContext)
{/*((EQuality.Framework.Dal.EFDbContext)_IDbContext).ConnStr((Microsoft.EntityFrameworkCore.DbContext)ss).Database.ProviderName*///- Database // {Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade} // Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacadevar dbFacade = dbContext.GetProperty<DatabaseFacade>("Database");string dbType = dbFacade.ProviderName; // db 类型string connStr = dbContext.Database.GetConnectionString(); // 获取 db 连接字符串IFreeSql fsql = FreeSqlHelper.GetInstance().FreeBuilder(dbType, connStr); // 单例构造器构建 IFreeSql 对象 return (dbContext, fsql);
}
此处项目【Jeff.Mes.EntityFrameworkCore
】中 TestContext.cs 类已经完成两种 orm
模式下新增 User
信息的操作,分别是:
AddUserAsync
,【EF Core
模式】添加用户测试;FreeSqlAddUserAsync
,【FreeSql
模式】添加用户测试;
到这里我们就可以使用 FreeSql
无缝替换 EF Core
的操作,同时也保留了 EF Core
模式的玩法,小伙伴们又可以继续愉快的玩耍了哟。
测试 orm 两种模式下的操作
添加 User 用户信息
新增控制台项目【Jeff.Mes.EntityFrameworkCore.Test
】,项目引用【Jeff.Mes.EntityFrameworkCore
】,添加如下代码:
namespace Jeff.Mes.EntityFrameworkCore.Test;internal class Program
{static async Task Main(string[] args){Console.WriteLine("Hello, EF Core & FreeSQL!");var test = new TestContext();int rcount1 = await test.AddUserAsync();string info1 = rcount1 > 0 ? "User 信息添加成功" : "User 信息添加失败";Console.WriteLine($"efcore 模式:{info1}");int rcount2 = await test.FreeSqlAddUserAsync();string info2 = rcount2 > 0 ? "User 信息添加成功" : "User 信息添加失败";Console.WriteLine($"fsql 模式:{info2}");Console.ReadKey();}
}
使用 vs 【工具 => 连接到数据库】测试数据库连接是否能正常通信访问,测试如下:
启动控制台,执行代码,输出如下信息:
Hello, EF Core & FreeSQL!
efcore 模式:User 信息添加成功
fsql 模式:User 信息添加成功
使用数据库客户端工具 DBeaver
查看:
FreeSql 查询 User 用户信息
使用 FreeSQL
查询 User
表信息:
/// <summary>
/// 【FreeSql 模式】查询用户信息
/// </summary>
/// <returns></returns>
public async Task<List<User>> FreeSqlGetUserAsync()
{using (var dbContext = new TestContext()){var (myDbContext, fsql) = GetDbContext(dbContext);return await fsql.Select<User>().ToListAsync();}
}
控制台 Main
函数中调用 FreeSqlGetUserAsync
方法:
关于 FreeSql
更多信息,请查看相关文档
- FreeSql 官方文档,https://freesql.net/
- FreeSql 博客文档,https://www.cnblogs.com/FreeSql/p/11531300.html
总结
在使用 EF Core
作为默认的 ORM
工具操作数据库时,项目中我们或许只能接触到 DbContext
对象,没法直接获取 db
数据库连接信息,假如有小伙伴想接入 FreeSQL
继续使用熟悉的模式,那该怎么办呢?此时我们可以这样操作,为了不影响原有项目结构的操作,又想接入 FreeSQL
的小伙伴们,通过上面的方式我们就可以使用 FreeSQL
无缝替换 EF Core
。
其实接入 FreeSQL
的方式很简单,只需具备两点条件即可,首先就是 有效的 db 连接字符串【dbContext.Database.GetConnectionString()
】,其次就是获取对应的 数据库类型【dbFacade.ProviderName
】。