使用EF Core 6执行原始SQL查询

Posted dreamw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用EF Core 6执行原始SQL查询相关的知识,希望对你有一定的参考价值。

使用EF Core 6执行原始SQL查询

目录

背景

现有选项

ExecuteSqlRaw

插入

更新

删除

FromSqlRaw

FromSqlInterpolated

自定义数据库上下文扩展方法

ExecuteScalar

ExecuteNonQuery

FromSqlQuery

FromSqlRaw

ExecuteScalar

ExecuteNonQuery

与事务一起使用

与事务范围一起使用

FromSqlQuery

模型

手动映射

使用索引

使用列名

自动映射

FromSqlRaw

使用扩展方法

局限性

引用

关于代码示例


 

背景

实体框架核心允许我们在使用关系数据库时下拉到原始SQL查询。此外,它还提供了使用ADO.NET功能直接对数据库执行原始SQL查询的机制。在这里,我们将探讨在实体框架核心中运行行SQL的现有选项和自定义选项,但将更多地关注使用ADO.NET的扩展方法实现。

现有选项

在实体框架核心中,有多个选项可用于运行原始SQL查询。要使用它们,我们需要安装Microsoft.EntityFrameworkCore.Relational和Microsoft.EntityFrameworkCore:

  1.  
    Install-Package Microsoft.EntityFrameworkCore
  2.  
    Install-Package Microsoft.EntityFrameworkCore.Relational
 

ExecuteSqlRaw

执行非查询 SQL。以下是一些insert、update和delete示例。参数化查询是可选的,如果需要,我们可以跳过它。

插入

  1.  
    object[] paramItems = new object[]
  2.  
  3.  
    new SqlParameter("@paramName", "Ben"),
  4.  
    new SqlParameter("@paramCreatedBy", "Ben"),
  5.  
    new SqlParameter("@paramCreatedOn", DateTime.UtcNow),
  6.  
    new SqlParameter("@paramIsDeleted", true),
  7.  
    ;
  8.  
    int items = Db.Database.ExecuteSqlRaw("INSERT INTO Users([Name],
  9.  
    [IsDeleted], CreatedOn, CreatedBy) VALUES (@paramName, @paramIsDeleted,
  10.  
    @paramCreatedOn, @paramCreatedBy)", paramItems);
 

更新

 

  1.  
    object[] paramItems = new object[]
  2.  
  3.  
    new SqlParameter("@paramEmail", "ben@gmail.com"),
  4.  
    new SqlParameter("@paramName", "Ben")
  5.  
    ;
  6.  
    int items = Db.Database.ExecuteSqlRaw
  7.  
    ("UPDATE Users SET Email = @paramEmail WHERE [Name] = @paramName", paramItems);
 

删除

 

  1.  
    object[] paramItems = new object[]
  2.  
  3.  
    new SqlParameter("@paramName", "Ben")
  4.  
    ;
  5.  
    int items = Db.Database.ExecuteSqlRaw("DELETE FROM Users
  6.  
    WHERE [Name] = @paramName", paramItems);
 

在3.1之前,有ExecuteSqlCommand。

FromSqlRaw<T>

选择数据并映射到现有DbSet<TSource>。

  1.  
    List<User> usersInDb = Db.Users.FromSqlRaw
  2.  
    (
  3.  
    "SELECT * FROM Users WHERE Name=@paramName",
  4.  
    new SqlParameter("@paramName", user.Name)
  5.  
    )
  6.  
    .ToList();
 

这仅适用于DbSet声明。下面Users是一个DbSet<T>,在DbContext中声明。

  1.  
    public class CpuAppDbContext : DbContext
  2.  
  3.  
    public DbSet<User> Users get; set;
  4.  
 

FromSqlInterpolated<T>

 

  1.  
    List<User> usersInDb = Db.Users.FromSqlInterpolated<User>
  2.  
    (
  3.  
    $"SELECT * FROM Users WHERE Name=user.Name"
  4.  
    )
  5.  
    .ToList();
 

自定义数据库上下文扩展方法

下面是一些用于运行原始SQL的扩展方法的DbContext和DatabaseFacade对象。在Database.Core项目的帮助程序类 EfSqlHelper.cs 中,我们将找到列出的扩展方法。

ExecuteScalar

  • 返回查询返回的结果集中第一行的第一列
  • 可选查询参数化
  • 可选命令类型和命令超时

ExecuteNonQuery

  • 执行不返回任何数据的原始SQL查询
  • 返回受影响的行数
  • 可选查询参数化
  • 可选命令类型和命令超时
  • 支持数据库交易

FromSqlQuery<T>

  • 执行返回数据的原始SQL查询
  • Mapp将数据行返回到给定类型T
    • 手动获取Mapp的数据
    • 自动获取Mapp的数据
  • 可选的查询参数化。
  • 可选命令类型和命令超时

FromSqlRaw<T>

  • 内置的通用包装器FromSqlRaw

ExecuteScalar

执行查询,并返回查询返回的结果集中第一行的第一列。其他列或行将被忽略。

  1.  
    using Microsoft.EntityFrameworkCore;
  2.  
    using Microsoft.EntityFrameworkCore.Infrastructure;
  3.  
    using Microsoft.EntityFrameworkCore.Storage;
  4.  
    using System;
  5.  
    using System.Collections.Generic;
  6.  
    using System.Data;
  7.  
    using System.Data.Common;
  8.  
    using System.Linq;
  9.  
    using System.Reflection;
  10.  
     
  11.  
    namespace EfCoreHelper.Database.Core
  12.  
  13.  
    public static class EfSqlHelper
  14.  
  15.  
    public static DbTransaction GetDbTransaction(this IDbContextTransaction source)
  16.  
  17.  
    return (source as IInfrastructure<DbTransaction>).Instance;
  18.  
  19.  
     
  20.  
    public static object ExecuteScalar(this DbContext context, string sql,
  21.  
    List<DbParameter> parameters = null,
  22.  
    CommandType commandType = CommandType.Text,
  23.  
    int? commandTimeOutInSeconds = null)
  24.  
  25.  
    Object value = ExecuteScalar(context.Database, sql, parameters,
  26.  
    commandType, commandTimeOutInSeconds);
  27.  
    return value;
  28.  
  29.  
     
  30.  
    public static object ExecuteScalar(this DatabaseFacade database,
  31.  
    string sql, List<DbParameter> parameters = null,
  32.  
    CommandType commandType = CommandType.Text,
  33.  
    int? commandTimeOutInSeconds = null)
  34.  
  35.  
    Object value;
  36.  
    using (var cmd = database.GetDbConnection().CreateCommand())
  37.  
  38.  
    if (cmd.Connection.State != ConnectionState.Open)
  39.  
  40.  
    cmd.Connection.Open();
  41.  
  42.  
    cmd.CommandText = sql;
  43.  
    cmd.CommandType = commandType;
  44.  
    if (commandTimeOutInSeconds != null)
  45.  
  46.  
    cmd.CommandTimeout = (int)commandTimeOutInSeconds;
  47.  
  48.  
    if (parameters != null)
  49.  
  50.  
    cmd.Parameters.AddRange(parameters.ToArray());
  51.  
  52.  
    value = cmd.ExecuteScalar();
  53.  
  54.  
    return value;
  55.  
  56.  
  57.  
 

在提取方法中,我们使用ADO.NET特征。从Ef DbContext的数据库对象,我们正在访问底层数据库连接对象并从中创建Db命令。然后将所有必需的参数分配给命令对象,如SQL、命令类型、SQL参数、使用现有数据库转换和可选命令超时到新创建的命令。最后,调用ExecuteScalar()以执行原始SQL查询。

  1.  
    int count = (int)Db.ExecuteScalar
  2.  
    (
  3.  
    "SELECT COUNT(1) FROM Users WHERE Name=@paramName",
  4.  
    new List<DbParameter>() new SqlParameter("@paramName", user.Name)
  5.  
    );
 

ExecuteNonQuery

对连接执行Transact-SQL语句,并返回受影响的行数。

  1.  
    using Microsoft.EntityFrameworkCore;
  2.  
    using Microsoft.EntityFrameworkCore.Infrastructure;
  3.  
    using Microsoft.EntityFrameworkCore.Storage;
  4.  
    using System;
  5.  
    using System.Collections.Generic;
  6.  
    using System.Data;
  7.  
    using System.Data.Common;
  8.  
    using System.Linq;
  9.  
    using System.Reflection;
  10.  
     
  11.  
    namespace EfCoreHelper.Database.Core
  12.  
  13.  
    public static class EfSqlHelper
  14.  
  15.  
    public static DbTransaction GetDbTransaction(this IDbContextTransaction source)
  16.  
  17.  
    return (source as IInfrastructure<DbTransaction>).Instance;
  18.  
  19.  
     
  20.  
    public static int ExecuteNonQuery(this DbContext context, string command,
  21.  
    List<DbParameter> parameters = null,
  22.  
    CommandType commandType = CommandType.Text,
  23.  
    int? commandTimeOutInSeconds = null)
  24.  
  25.  
    int value = ExecuteNonQuery(context.Database, command,
  26.  
    parameters, commandType, commandTimeOutInSeconds);
  27.  
    return value;
  28.  
  29.  
     
  30.  
    public static int ExecuteNonQuery(this DatabaseFacade database,
  31.  
    string command, List<DbParameter> parameters = null,
  32.  
    CommandType commandType = CommandType.Text,
  33.  
    int? commandTimeOutInSeconds = null)
  34.  
  35.  
    using (var cmd = database.GetDbConnection().CreateCommand())
  36.  
  37.  
    if (cmd.Connection.State != ConnectionState.Open)
  38.  
  39.  
    cmd.Connection.Open();
  40.  
  41.  
    var currentTransaction = database.CurrentTransaction;
  42.  
    if (currentTransaction != null)
  43.  
  44.  
    cmd.Transaction = currentTransaction.GetDbTransaction();
  45.  
  46.  
    cmd.CommandText = command;
  47.  
    cmd.CommandType = commandType;
  48.  
    if (commandTimeOutInSeconds != null)
  49.  
  50.  
    cmd.CommandTimeout = (int)commandTimeOutInSeconds;
  51.  
  52.  
    if (parameters != null)
  53.  
  54.  
    cmd.Parameters.AddRange(parameters.ToArray());
  55.  
  56.  
    return cmd.ExecuteNonQuery();
  57.  
  58.  
  59.  
  60.  
 

提取方法与前一种非常相似。从DbContext的数据库对象中,创建Db命令。然后,将所有必需的参数分配给命令对象,例如SQL、命令类型、SQL参数、使用现有数据库转换以及命令的可选命令超时。最后,调用ExecuteNonQuery()以执行原始SQL查询。

  1.  
    Db.ExecuteNonQuery("DELETE FROM Users WHERE Id < @paramId", new List<DbParameter>()
  2.  
    new SqlParameter("@paramId", user.Id) );
 

与事务一起使用

 

  1.  
    Exception error = null;
  2.  
    using (var tran = Db.Database.BeginTransaction())
  3.  
  4.  
    try
  5.  
  6.  
    Db.ExecuteNonQuery("UPDATE Users SET Email =
  7.  
    @paramEmail WHERE Id = @paramId", new List<DbParameter>()
  8.  
    new SqlParameter("@paramEmail", newEmailOfOldUser),
  9.  
    new SqlParameter("@paramId", oldUser.Id) );
  10.  
    Db.ExecuteNonQuery("UPDATE Users SET Email =
  11.  
    @paramEmail WHERE Id = @paramId", new List<DbParameter>()
  12.  
    new SqlParameter("@paramEmail", newEmailOfUser),
  13.  
    new SqlParameter("@paramId", user.Id) );
  14.  
    tran.Commit();
  15.  
  16.  
    catch (Exception ex)
  17.  
  18.  
    error = ex;
  19.  
    tran.Rollback();
  20.  
  21.  
 

与事务范围一起使用

 

  1.  
    Exception error = null;
  2.  
    using (var scope = new TransactionScope())
  3.  
  4.  
    try
  5.  
  6.  
    Db.ExecuteNonQuery("UPDATE Users SET Email =
  7.  
    @paramEmail WHERE Id = @paramId", new List<DbParameter>()
  8.  
    new SqlParameter("@paramEmail", newEmailOfOldUser),
  9.  
    new SqlParameter("@paramId", oldUser.Id) );
  10.  
    Db.ExecuteNonQuery("UPDATE Users SET Email = @paramEmail WHERE Id = @paramId",
  11.  
    new List<DbParameter>() new SqlParameter("@paramEmail", newEmailOfUser),
  12.  
    new SqlParameter("@paramId", user.Id) );
  13.  
    scope.Complete();
  14.  
  15.  
    catch (Exception ex)
  16.  
  17.  
    error = ex;
  18.  
  19.  
 

FromSqlQuery<T>

创建一个原始SQL查询,该查询将返回给定泛型类型的元素。在较旧的实体框架版本中,曾经执行类似操作的是Database.SqlQuery<T>,但在较新版本/核心中被删除。现在,可以通过两种方式完成此泛型类型映射:

  • 手动映射数据
  • 自动映射数据

 

  1.  
    using Microsoft.EntityFrameworkCore;
  2.  
    using Microsoft.EntityFrameworkCore.Infrastructure;
  3.  
    using Microsoft.EntityFrameworkCore.Storage;
  4.  
    using System;
  5.  
    using System.Collections.Generic;
  6.  
    using System.Data;
  7.  
    using System.Data.Common;
  8.  
    using System.Linq;
  9.  
    using System.Reflection;
  10.  
     
  11.  
    namespace EfCoreHelper.Database.Core
  12.  
  13.  
    public static class EfSqlHelper
  14.  
  15.  
    private class PropertyMapp
  16.  
  17.  
    public string Name get; set;
  18.  
    public Type Type get; set;
  19.  
    public bool IsSame(PropertyMapp mapp)
  20.  
  21.  
    if (mapp == null)
  22.  
  23.  
    return false;
  24.  
  25.  
    bool same = mapp.Name == Name && mapp.Type == Type;
  26.  
    return same;
  27.  
  28.  
  29.  
     
  30.  
    public static DbTransaction GetDbTransaction(this IDbContextTransaction source)
  31.  
  32.  
    return (source as IInfrastructure<DbTransaction>).Instance;
  33.  
  34.  
     
  35.  
    public static IEnumerable<T> FromSqlQuery<T>
  36.  
    (this DbContext context, string query, List<DbParameter> parameters = null,
  37.  
    CommandType commandType = CommandType.Text,
  38.  
    int? commandTimeOutInSeconds = null) where T : new()
  39.  
  40.  
    return FromSqlQuery<T>(context.Database, query, parameters,
  41.  
    commandType, commandTimeOutInSeconds);
  42.  
  43.  
     
  44.  
    public static IEnumerable<T> FromSqlQuery<T>
  45.  
    (this DatabaseFacade database, string query,
  46.  
    List<DbParameter> parameters = null,
  47.  
    CommandType commandType = CommandType.Text,
  48.  
    int? commandTimeOutInSeconds = null) where T : new()
  49.  
  50.  
    const BindingFlags flags = BindingFlags.Public |
  51.  
    BindingFlags.Instance | BindingFlags.NonPublic;
  52.  
    List<PropertyMapp> entityFields = (from PropertyInfo aProp
  53.  
    in typeof(T).GetProperties(flags)
  54.  
    select new PropertyMapp
  55.  
  56.  
    Name = aProp.Name,
  57.  
    Type = Nullable.GetUnderlyingType
  58.  
    (aProp.PropertyType) ?? aProp.PropertyType
  59.  
    ).ToList();
  60.  
    List<PropertyMapp> dbDataReaderFields = new List<PropertyMapp>();
  61.  
    List<PropertyMapp> commonFields = null;
  62.  
     
  63.  
    using (var command = database.GetDbConnection().CreateCommand())
  64.  
  65.  
    if (command.Connection.State != ConnectionState.Open)
  66.  
  67.  
    command.Connection.Open();
  68.  
  69.  
    var currentTransaction = database.CurrentTransaction;
  70.  
    if (currentTransaction != null)
  71.  
  72.  
    command.Transaction = currentTransaction.GetDbTransaction();
  73.  
  74.  
    command.CommandText = query;
  75.  
    command.CommandType = commandType;
  76.  
    if (commandTimeOutInSeconds != null)
  77.  
  78.  
    command.CommandTimeout = (int)commandTimeOutInSeconds;
  79.  
  80.  
    if (parameters != null)
  81.  
  82.  
    command.Parameters.AddRange(parameters.ToArray());
  83.  
  84.  
    using (var result = command.ExecuteReader())
  85.  
  86.  
    while (result.Read())
  87.  
  88.  
    if (commonFields == null)
  89.  
  90.  
    for (int i = 0; i < result.FieldCount; i++)
  91.  
  92.  
    dbDataReaderFields.Add(new PropertyMapp
  93.  
    Name = result.GetName(i),
  94.  
    Type = result.GetFieldType(i) );
  95.  
  96.  
    commonFields = entityFields.Where
  97.  
    (x => dbDataReaderFields.Any(d =>
  98.  
    d.IsSame(x))).Select(x => x).ToList();
  99.  
  100.  
     
  101.  
    var entity = new T();
  102.  
    foreach (var aField in commonFields)
  103.  
  104.  
    PropertyInfo propertyInfos =
  105.  
    entity.GetType().GetProperty(aField.Name);
  106.  
    var value = (result[aField.Name] == DBNull.Value) ?
  107.  
    null : result[aField.Name]; //if field is nullable
  108.  
    propertyInfos.SetValue(entity, value, null);
  109.  
  110.  
    yield return entity;
  111.  
  112.  
  113.  
  114.  
  115.  
     
  116.  
    public static IEnumerable<T> FromSqlQuery<T>
  117.  
    (this DbContext context, string query, Func<DbDataReader, T> map,
  118.  
    List<DbParameter> parameters = null, CommandType commandType = CommandType.Text,
  119.  
    int? commandTimeOutInSeconds = null)
  120.  
  121.  
    return FromSqlQuery(context.Database, query, map, parameters,
  122.  
    commandType, commandTimeOutInSeconds);
  123.  
  124.  
     
  125.  
    public static IEnumerable<T> FromSqlQuery<T>
  126.  
    (this DatabaseFacade database, string query, Func<DbDataReader, T> map,
  127.  
    List<DbParameter> parameters = null,
  128.  
    CommandType commandType = CommandType.Text,
  129.  
    int? commandTimeOutInSeconds = null)
  130.  
  131.  
    using (var command = database.GetDbConnection().CreateCommand())
  132.  
  133.  
    if (command.Connection.State != ConnectionState.Open)
  134.  
  135.  
    command.Connection.Open();
  136.  
  137.  
    var currentTransaction = database.CurrentTransaction;
  138.  
    if (currentTransaction != null)
  139.  
  140.  
    command.Transaction = currentTransaction.GetDbTransaction();
  141.  
  142.  

    以上是关于使用EF Core 6执行原始SQL查询的主要内容,如果未能解决你的问题,请参考以下文章

    带有 EF Core 和内存数据库提供程序的原始 sql

    在 EF6 中执行复杂的原始 SQL 查询

    EF Core 执行sql语句

    MiniProfiler使用点滴记录-2017年6月23日11:08:23

    查缺补漏系统学习 EF Core 6 - 修改实体数据

    EF Core中执行Sql语句查询操作之FromSql,ExecuteSqlCommand,SqlQuery