EF Core框架数据库连接管理

**1、SQL Server:**SqlConnection、SqlCommand、SqlDataReader;

**2、SQLite:**SqliteConnection、SqliteCommand、SqliteDataReader;

**3、PostgreSQL:**NpgsqlConnection、NpgsqlCommand、NpgsqlDataReader;

其中,SQL Server 和 PostgreSQL 是有 DataAdapter 的,分别是 SqlDataAdapter 和 NpgsqlDataAdapter。SQLite 没有提供。

这样就保证了尽管面向不同的数据库,但 API 的调用过程差不多:

A、实例化 XXXConnection;

B、创建 XXXCommand 实例,设置 SQL 语句;

C、XXXCommand 实例调用 ExecuteXXX 执行 SQL,可能不需要返回结果,也可能需要 XXXDataReader 来读取结果;

D、关闭 XXXConnection 对象。

由于各种数据库相关的连接对象都是 DbConnection 的子类,于是,在连接管理上,只要统使用 DbConnection 类型就能把连接管理抽象出来,统一描述。为了实现这个"宏大目标",EF Core 在面向关系数据库专用包(xxxx.Relational.dll)中提供了 IRelationalConnection接口。这个接口完成了以下规范:

**1、ConnectionString 属性:**通过它,你可以设置 / 获取连接字符串;

**2、DbConnection 属性:**这个很重要,有了此属性,就可以设置 / 获取连接对象了,定义的类型正是公共基类 DbConnection;

**3、Open / OpenAsync 方法:**打开连接;

**4、Close / CloseAsync 方法:**关闭连接;

**5、RentCommand 方法:**调用它,它会自动帮你创建/重用一个命令实例,用 IRelationalCommand 接口封装了。这个接口稍后再介绍;

**6、ReturnCommand 方法:**命令对象使用完后,可以调用这个方法,把实例仍回去,以便将来可以重复/或全新使用。框架帮你管理其内存,不用你操心。

有小伙伴会疑惑:咦,我在 EFCore 源代码中搜索 SqlCommand、SqliteCommand 等关键字,居然找不到它在哪里使用命令。你不要忘了,DbConnection 类有个叫 CreateCommand 的方法,所有派生类都实现这个方法。SqlConnection 类会让它返回 SqlCommand 实例,SqliteConnection 类会让它返回 SqliteCommand 实例。但由于 CreateCommand 方法定义的返回类型是 DbCommand 类,因此它有通用性。EF Core 中就是调用了 CreateCommand 方法来获得命令实例的。这就不得不提前文中出现的一个接口了------ IRelationalCommand。它统一了一些方法:

**1、ExecuteNonQuery / ExecuteNonQueryAsync 方法:**执行命令,通常不返回查询结果,如 INSERT、DELETE 等;

**2、ExecuteReader / ExecuteReaderAsync 方法:**执行后会有查询结果,但 XXXDataReader 类被 RelationalDataReader 类封装了;

**3、ExecuteScalar / ExecuteScalarAsync 方法:**返回单个值。

IRelationalCommand 接口的实现类是 RelationalCommand。它有个 CreateDbCommand 公共方法。

复制代码
    public virtual DbCommand CreateDbCommand(
        RelationalCommandParameterObject parameterObject,
        Guid commandId,
        DbCommandMethod commandMethod)
    {
        var (connection, context, logger) = (parameterObject.Connection, parameterObject.Context, parameterObject.Logger);
        var connectionId = connection.ConnectionId;

        var startTime = DateTimeOffset.UtcNow;

        DbCommand command;

        var stopwatch = SharedStopwatch.StartNew();

        var logCommandCreate = logger?.ShouldLogCommandCreate(startTime) == true;
        if (logCommandCreate)
        {
            var interceptionResult = logger!.CommandCreating(
                connection, commandMethod, context, commandId, connectionId, startTime,
                parameterObject.CommandSource);

            command = interceptionResult.HasResult
                ? interceptionResult.Result
                : connection.DbConnection.CreateCommand();

            command = logger.CommandCreated(
                connection,
                command,
                commandMethod,
                context,
                commandId,
                connectionId,
                startTime,
                stopwatch.Elapsed,
                parameterObject.CommandSource);
        }
        else
        {
            command = connection.DbConnection.CreateCommand();
        }

        command.CommandText = CommandText;

        if (connection.CurrentTransaction != null)
        {
            command.Transaction = connection.CurrentTransaction.GetDbTransaction();
        }

        if (connection.CommandTimeout != null)
        {
            command.CommandTimeout = (int)connection.CommandTimeout;
        }

        for (var i = 0; i < Parameters.Count; i++)
        {
            Parameters[i].AddDbParameter(command, parameterObject.ParameterValues);
        }

        if (logCommandCreate)
        {
            command = logger!.CommandInitialized(
                connection,
                command,
                commandMethod,
                context,
                commandId,
                connectionId,
                startTime,
                stopwatch.Elapsed,
                parameterObject.CommandSource);
        }

        return command;
    }

好了,现在基本的原理通了,咱们回到 IRelationalConnection 接口,它有一个抽象类实现:RelationalConnection。这个类中定义了一个抽象方法叫 CreateDbConnection,各种数据库在匹配 API 时会重写此方法。比如:

A、SQLite 数据库提供者,从 RelationalConnection 派生出 SqliteRelationalConnection 类,重写 CreateDbConnection 方法。

复制代码
protected override DbConnection CreateDbConnection()
{
    var connection = new SqliteConnection(GetValidatedConnectionString());
    InitializeDbConnection(connection);

    return connection;
}

B、SQL Server 数据提供者:从 RelationalConnection 派生出 SqlServerConnection 类,重写 CreateDbConnection 方法。

复制代码
protected override DbConnection CreateDbConnection()
    => new SqlConnection(GetValidatedConnectionString());

C、PostgreSQL 数据库提供者:从 RelationalConnection 派生出 NpgsqlRelationalConnection 类,重写 CreateDbConnection 方法。

复制代码
    protected override DbConnection CreateDbConnection()
    {
        if (DataSource is not null)
        {
            return DataSource.CreateConnection();
        }

        var conn = new NpgsqlConnection(ConnectionString);

        if (_provideClientCertificatesCallback is not null || _remoteCertificateValidationCallback is not null)
        {
            conn.SslClientAuthenticationOptionsCallback = o =>
            {
                if (_provideClientCertificatesCallback is not null)
                {
                    o.ClientCertificates ??= new();
                    _provideClientCertificatesCallback(o.ClientCertificates);
                }

                o.RemoteCertificateValidationCallback = _remoteCertificateValidationCallback;
            };
        }

        if (_providePasswordCallback is not null)
        {
#pragma warning disable 618 // ProvidePasswordCallback is obsolete
            conn.ProvidePasswordCallback = _providePasswordCallback;
#pragma warning restore 618
        }

        return conn;
    }

每个数据库提供者都会把实现 IRelationalConnection 接口的类注册到服务容器中,也就是说,咱们在应用代码中是可以访问此接口的功能的。

复制代码
// 构建连接字符串
SqliteConnectionStringBuilder csbuilder = new();
csbuilder.DataSource = "test.db";
csbuilder.Password = "huhuhahe";
// 构建选项
DbContextOptions<DbContext> options = new DbContextOptionsBuilder<DbContext>()
    .UseSqlite(csbuilder.ConnectionString)
    .Options;

// 此处只用来测试 IRelationalConnection 服务的访问、
// 所以无实体类无 DbContext 的派生类
using DbContext context = new(options);

// 获取服务
IRelationalConnection conn = context.GetService<IRelationalConnection>();
// 打印连接字符串
Console.WriteLine($"连接字符串:{conn.ConnectionString}");

代码运行后,输出结果如下:

复制代码
连接字符串:Data Source=test.db;Password=huhuhahe

上面的示例其实没啥鸟用,接下来老周讲个比较有实用性的。下面咱们看看共享 DbConnection。

实体和 Context 如下:

复制代码
public class Dog
{
    public Guid DogId { get; set; }
    public string Name { get; set; } = "Who?";
    public int Age { get; set; }
    public string? Category { get; set; }
}

public class MyDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        EntityTypeBuilder<Dog> tb = modelBuilder.Entity<Dog>();
        tb.HasKey(d => d.DogId).HasName("PK_Dog");
        tb.ToTable("tb_dogs");
        tb.Property(x => x.Name).HasMaxLength(20).IsRequired();
        tb.Property(a => a.Category).HasDefaultValue("未确认");
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite(_connection);
    }

    DbConnection _connection;
    public MyDbContext(DbConnection c)
    {
        _connection = c;
    }

    public DbSet<Dog> DogSet { get; set; }
}

注意这个 MyDbContext 类,它的构造函数可以传递 DbConnection 对象,然后在重写的 OnConfiguring 方法中调用 UseSqlite 扩展方法直接引用外部的 DbConnection 对象。这样就实现了连接对象的共享。接着看代码:

复制代码
 static void Main(string[] args)
 {
     SqliteConnection myconn = new("data source=mme.db");
     // 初始化数据库
     using (MyDbContext ctx = new(myconn))
     {
         ctx.Database.EnsureDeleted();
         ctx.Database.EnsureCreated();
     }
     // 插入数据
     using (var ctx = new MyDbContext(myconn))
     {
         ctx.DogSet.Add(new()
         {
             Name = "小菜",
             Age = 2,
             Category = "吉娃娃"
         });
         ctx.DogSet.Add(new()
         {
             Name = "Jimy",
             Age = 3,
             Category = "贵宾犬"
         });
         ctx.SaveChanges();
     }
     // 查询数据
     using (MyDbContext c = new MyDbContext(myconn))
     {
         foreach (Dog d in c.DogSet)
         {
             Console.WriteLine($"{d.Name} - {d.Category}, {d.Age}岁
");
         }
     }
 }
先实例化连接对象,然后依次传递三个 MyDbContext 实例使用。
相关推荐
熙客2 小时前
分布式ID解决方案
java·分布式·spring cloud·微服务
菜鸟小九2 小时前
SSM(MybatisPlus)
java·开发语言·spring boot·后端
不爱编程的小九九2 小时前
小九源码-springboot051-智能推荐旅游平台
java·spring boot·后端
期待のcode3 小时前
MyBatis框架—延迟加载与多级缓存
java·数据库·后端·缓存·mybatis
老华带你飞3 小时前
小区服务|基于Java+vue的小区服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·小区服务管理系统
华仔啊3 小时前
Spring 配置混乱?搞懂这两个核心组件,问题真能少一半
java·后端·spring
喂完待续3 小时前
【序列晋升】45 Spring Data Elasticsearch 实战:3 个核心方案破解索引管理与复杂查询痛点,告别低效开发
java·后端·spring·big data·spring data·序列晋升
郑重其事,鹏程万里3 小时前
commons-exec
java
龙茶清欢3 小时前
具有实际开发参考意义的 MyBatis-Plus BaseEntity 基类示例
java·spring boot·spring cloud·mybatis