【问题标题】:Raw SQL Query without DbSet - Entity Framework Core没有 DbSet 的原始 SQL 查询 - 实体框架核心
【发布时间】:2024-07-11 04:30:02
【问题描述】:

使用 Entity Framework Core 删除 dbData.Database.SqlQuery<SomeModel> 我找不到为我的全文搜索查询构建原始 SQL 查询的解决方案,该查询将返回表数据和排名。

我见过的在 Entity Framework Core 中构建原始 SQL 查询的唯一方法是通过 dbData.Product.FromSql("SQL SCRIPT");,因为我没有 DbSet 来映射我在查询中返回的排名。

有什么想法吗???

【问题讨论】:

  • 我会非常想念 SqlQuery,并且当我真的只需要一个用于特定用例的简单 DTO 时,我不想将自定义类映射到我的 DbContext。我创建了一个用户声音来请求将此功能重新添加到 EF Core,如果他们想要此功能,任何人都可以投票:data.uservoice.com/forums/…
  • 根据github.com/aspnet/EntityFramework/issues/1862,这现在针对 EF 核心 1.2 和/或 1.1.0-preview1
  • 基于@Devon 刚才所说的,我花了很长时间才弄清楚它们是 Microsoft.EntityFrameworkCore.SqlServer 中的扩展方法。在获取这些扩展方法之前,您需要将其添加到您的项目中。
  • 叹息这似乎是某种建筑宇航员的决定:“人们不应该需要这个”。我想我必须为这种情况安装 Dapper。烦人。
  • @MattSanders - 您的用户语音链接似乎在此期间已失效。你知道它去哪儿了吗?

标签: c# entity-framework-core


【解决方案1】:

在 EF Core 中,您不再可以执行“免费”原始 sql。您需要为该类定义一个 POCO 类和一个 DbSet。 在您的情况下,您需要定义 Rank

var ranks = DbContext.Ranks
   .FromSql("SQL_SCRIPT OR STORED_PROCEDURE @p0,@p1,...etc", parameters)
   .AsNoTracking().ToList();

因为它肯定是只读的,所以包含 .AsNoTracking() 调用会很有用。

编辑 - EF Core 3.0 中的重大变化:

DbQuery() 现在已过时,应该(再次)使用 DbSet()。如果你有一个无键实体,即它不需要主键,你可以使用 HasNoKey() 方法:

ModelBuilder.Entity<SomeModel>().HasNoKey()

更多信息可以找到here

【讨论】:

  • 所以我想我还必须扩展DbContext 以包含一个新属性DbSet&lt;Rank&gt; Rank { get; set; }。现在这对 linq 有什么影响? IE。难道我们现在不能使用像DBContext.Rank.Where(i =&gt; i.key == 1) 这样的语句,而且这个语句不会在 SQL 中没有实现,因此会失败吗?
  • 针对该集合发出的 Linq 必须在内存中解析。如果您需要发出不同的 WHERE sql 子句,则必须将它们作为参数包含在内或构建不同的脚本。
  • 我的 DbSet 没有“FromSql”方法。这是我缺少的扩展吗?
  • @birwin,你需要导入命名空间Microsoft.EntityFrameworkCore
【解决方案2】:

您可以在 EF Core 中执行原始 sql - 将此类添加到您的项目中。 这将允许您执行原始 SQL 并获得原始结果,而无需定义 POCO 和 DBSet。 原始示例见https://github.com/aspnet/EntityFramework/issues/1862#issuecomment-220787464

using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.EntityFrameworkCore
{
    public static class RDFacadeExtensions
    {
        public static RelationalDataReader ExecuteSqlQuery(this DatabaseFacade databaseFacade, string sql, params object[] parameters)
        {
            var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();

            using (concurrencyDetector.EnterCriticalSection())
            {
                var rawSqlCommand = databaseFacade
                    .GetService<IRawSqlCommandBuilder>()
                    .Build(sql, parameters);

                return rawSqlCommand
                    .RelationalCommand
                    .ExecuteReader(
                        databaseFacade.GetService<IRelationalConnection>(),
                        parameterValues: rawSqlCommand.ParameterValues);
            }
        }

        public static async Task<RelationalDataReader> ExecuteSqlQueryAsync(this DatabaseFacade databaseFacade, 
                                                             string sql, 
                                                             CancellationToken cancellationToken = default(CancellationToken),
                                                             params object[] parameters)
        {

            var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();

            using (concurrencyDetector.EnterCriticalSection())
            {
                var rawSqlCommand = databaseFacade
                    .GetService<IRawSqlCommandBuilder>()
                    .Build(sql, parameters);

                return await rawSqlCommand
                    .RelationalCommand
                    .ExecuteReaderAsync(
                        databaseFacade.GetService<IRelationalConnection>(),
                        parameterValues: rawSqlCommand.ParameterValues,
                        cancellationToken: cancellationToken);
            }
        }
    }
}

这是一个如何使用它的示例:

// Execute a query.
using(var dr = await db.Database.ExecuteSqlQueryAsync("SELECT ID, Credits, LoginDate FROM SamplePlayer WHERE " +
                                                          "Name IN ('Electro', 'Nitro')"))
{
    // Output rows.
    var reader = dr.DbDataReader;
    while (reader.Read())
    {
        Console.Write("{0}\t{1}\t{2} \n", reader[0], reader[1], reader[2]);
    }
}

【讨论】:

    【解决方案3】:

    目前,在 EFCore 有新内容之前,我会使用命令 并手动映射它

      using (var command = this.DbContext.Database.GetDbConnection().CreateCommand())
      {
          command.CommandText = "SELECT ... WHERE ...> @p1)";
          command.CommandType = CommandType.Text;
          var parameter = new SqlParameter("@p1",...);
          command.Parameters.Add(parameter);
    
          this.DbContext.Database.OpenConnection();
    
          using (var result = command.ExecuteReader())
          {
             while (result.Read())
             {
                .... // Map to your entity
             }
          }
      }
    

    尽量使用SqlParameter来避免Sql Injection。

     dbData.Product.FromSql("SQL SCRIPT");
    

    FromSql 不适用于完整查询。例如,如果您想包含 WHERE 子句,它将被忽略。

    一些链接:

    Executing Raw SQL Queries using Entity Framework Core

    Raw SQL Queries

    【讨论】:

      【解决方案4】:

      在其他答案的基础上,我编写了这个帮助程序来完成任务,包括示例用法:

      public static class Helper
      {
          public static List<T> RawSqlQuery<T>(string query, Func<DbDataReader, T> map)
          {
              using (var context = new DbContext())
              {
                  using (var command = context.Database.GetDbConnection().CreateCommand())
                  {
                      command.CommandText = query;
                      command.CommandType = CommandType.Text;
      
                      context.Database.OpenConnection();
      
                      using (var result = command.ExecuteReader())
                      {
                          var entities = new List<T>();
      
                          while (result.Read())
                          {
                              entities.Add(map(result));
                          }
      
                          return entities;
                      }
                  }
              }
          }
      

      用法:

      public class TopUser
      {
          public string Name { get; set; }
      
          public int Count { get; set; }
      }
      
      var result = Helper.RawSqlQuery(
          "SELECT TOP 10 Name, COUNT(*) FROM Users U"
          + " INNER JOIN Signups S ON U.UserId = S.UserId"
          + " GROUP BY U.Name ORDER BY COUNT(*) DESC",
          x => new TopUser { Name = (string)x[0], Count = (int)x[1] });
      
      result.ForEach(x => Console.WriteLine($"{x.Name,-25}{x.Count}"));
      

      我计划在添加内置支持后立即删除它。根据 EF Core 团队的 Arthur Vickers 的statement,它是 2.0 后的高优先级。正在跟踪该问题here

      【讨论】:

      • 不错的答案,喜欢它。
      • 还能安全地添加参数吗?
      • 自 EF 核心 3.1 docs.microsoft.com/en-us/ef/core/modeling/… 起 HasNoKey/ Keyless 实体类型可用,您的助手是否已过时?
      • 如果现在有内置方法,如果您可以更新您的帖子会很棒
      【解决方案5】:

      这取决于您使用的是 EF Core 2.1 还是 EF Core 3 及更高版本

      如果你使用的是 EF Core 2.1

      如果您使用自 2018 年 5 月 7 日起可用的 EF Core 2.1 Release Candidate 1,您可以利用提议的新功能,即查询类型。

      query type 是什么?

      除了实体类型之外,EF Core 模型还可以包含查询类型, 可用于针对以下数据执行数据库查询 没有映射到实体类型。

      什么时候使用查询类型?

      作为临时 FromSql() 查询的返回类型。

      映射到数据库视图。

      映射到没有定义主键的表。

      映射到模型中定义的查询。

      因此,您不再需要执行所有建议的技巧或解决方法来回答您的问题。只需按照以下步骤操作:

      首先,您定义了一个DbQuery&lt;T&gt; 类型的新属性,其中T 是将携带SQL 查询的列值的类的类型。所以在你的DbContext 中你会有这个:

      public DbQuery<SomeModel> SomeModels { get; set; }
      

      第二次使用 FromSql 方法,就像使用 DbSet&lt;T&gt; 一样:

      var result = context.SomeModels.FromSql("SQL_SCRIPT").ToList();
      var result = await context.SomeModels.FromSql("SQL_SCRIPT").ToListAsync();
      

      还要注意DdContexts 是partial classes,因此您可以创建一个或多个单独的文件来组织最适合您的“原始 SQL DbQuery”定义。


      如果你使用的是 EF Core 3.0 及更高版本

      查询类型现在称为Keyless entity type。如上所述,查询类型是在 EF Core 2.1 中引入的。如果您使用的是 EF Core 3.0 或更高版本,您现在应该考虑使用无键实体类型,因为查询类型现在被标记为过时。

      此功能是在 EF Core 2.1 中以查询类型的名称添加的。 在 EF Core 3.0 中,该概念被重命名为无键实体类型。这 [Keyless] 数据注释在 EFCore 5.0 中可用。

      对于何时使用无键实体类型,我们仍然有与查询类型相同的场景。

      所以要使用它,你需要先用[Keyless]数据注释标记你的类SomeModel,或者通过.HasNoKey()方法调用的流畅配置,如下所示:

      public DbSet<SomeModel> SomeModels { get; set; }
      
      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
          modelBuilder.Entity<SomeModel>().HasNoKey();
      }
      

      配置完成后,您可以使用here 解释的方法之一来执行您的 SQL 查询。例如你可以使用这个:

      var result = context.SomeModels.FromSqlRaw("SQL SCRIPT").ToList();
      var result = await context.SomeModels.FromSql("SQL_SCRIPT").ToListAsync();
      

      【讨论】:

      • 这个答案应该是使用 EF Core 2.1 及更高版本时的最佳解决方案。 ?
      • 使用 CodeFirst 这会自动创建一个包含所有这些属性的表,将 [NotMapped] 添加到 SomeModels 类对我不起作用。我错过了什么吗?
      • EF Core 3.0 弃用 DbQuery,而只支持使用 DbSetkeyless entity types
      • 仅供参考,由于 EF Core 3.0 中的一些错误,即使在标有 HasNoKey() 的实体上,代码优先迁移仍将尝试创建表。因此,您还必须添加 .ToView(null)。例如。 modelBuilder.Entity&lt;MyData&gt;().HasNoKey().ToView(null);@Jean-Paul 我认为这可以解决您的问题
      • @AnthonyGriggs 版本 5 支持这个 ... modelBuilder.Entity().ToTable("ApplicationUsers", t => t.ExcludeFromMigrations());
      【解决方案6】:

      在 Core 2.1 中,您可以执行以下操作:

      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
             modelBuilder.Query<Ranks>();
      }
      

      然后定义你的 SQL 过程,比如:

      public async Task<List<Ranks>> GetRanks(string value1, Nullable<decimal> value2)
      {
          SqlParameter value1Input = new SqlParameter("@Param1", value1?? (object)DBNull.Value);
          SqlParameter value2Input = new SqlParameter("@Param2", value2?? (object)DBNull.Value);
      
          List<Ranks> getRanks = await this.Query<Ranks>().FromSql("STORED_PROCEDURE @Param1, @Param2", value1Input, value2Input).ToListAsync();
      
          return getRanks;
      }
      

      这种方式不会在您的数据库中创建排名模型。

      现在您可以在控制器/动作中调用:

      List<Ranks> gettingRanks = _DbContext.GetRanks(value1,value2).Result.ToListAsync();
      

      这样您就可以调用原始 SQL 过程。

      【讨论】:

      • FromSql 参数可以简单地传递而无需创建 SqlParameter 对象:FromSql($"STORED_PROCEDURE {value1}, {value2}")FromSql("STORED_PROCEDURE {0}, {1}", value1, value2)(它们将被转义)。
      【解决方案7】:

      我使用Dapper 绕过了Entity framework Core 的这个约束。

      IDbConnection.Query
      

      正在使用带有多个参数的 sql 查询或存储过程。 顺便说一下,它有点快(见benchmark tests

      Dapper 易于学习。编写和运行带参数的存储过程花了 15 分钟。无论如何,您可以同时使用 EF 和 Dapper。下面是一个例子:

       public class PodborsByParametersService
      {
          string _connectionString = null;
      
      
          public PodborsByParametersService(string connStr)
          {
              this._connectionString = connStr;
      
          }
      
          public IList<TyreSearchResult> GetTyres(TyresPodborView pb,bool isPartner,string partnerId ,int pointId)
          {
      
              string sqltext  "spGetTyresPartnerToClient";
      
              var p = new DynamicParameters();
              p.Add("@PartnerID", partnerId);
              p.Add("@PartnerPointID", pointId);
      
              using (IDbConnection db = new SqlConnection(_connectionString))
              {
                  return db.Query<TyreSearchResult>(sqltext, p,null,true,null,CommandType.StoredProcedure).ToList();
              }
      
      
              }
      }
      

      【讨论】:

        【解决方案8】:

        您也可以使用QueryFirst。像 Dapper 一样,这完全在 EF 之外。与 Dapper(或 EF)不同,您不需要维护 POCO,您可以在真实环境中编辑您的 sql SQL,并且它会不断地针对 DB 重新验证。免责声明:我是 QueryFirst 的作者。

        【讨论】:

          【解决方案9】:

          添加 Nuget 包 - Microsoft.EntityFrameworkCore.Relational

          using Microsoft.EntityFrameworkCore;
          ...
          await YourContext.Database.ExecuteSqlCommandAsync("... @p0, @p1", param1, param2 ..)
          

          这会将行号作为整数返回

          见 - https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.executesqlcommand?view=efcore-3.0

          【讨论】:

          • 请注意,这只会返回受命令影响的行数:*.com/a/49861799/299756
          • 正是我需要的。我正在使用 Microsoft.EntityFrameworkCore 3.1.1 并且无法执行 RAW 查询和 SP。非常感谢您!
          【解决方案10】:

          不直接针对 OP 的场景,但由于我一直在努力解决这个问题,我想放弃这些 ex。使用DbContext 更容易执行原始 SQL 的方法:

          public static class DbContextCommandExtensions
          {
            public static async Task<int> ExecuteNonQueryAsync(this DbContext context, string rawSql,
              params object[] parameters)
            {
              var conn = context.Database.GetDbConnection();
              using (var command = conn.CreateCommand())
              {
                command.CommandText = rawSql;
                if (parameters != null)
                  foreach (var p in parameters)
                    command.Parameters.Add(p);
                await conn.OpenAsync();
                return await command.ExecuteNonQueryAsync();
              }
            }
          
            public static async Task<T> ExecuteScalarAsync<T>(this DbContext context, string rawSql,
              params object[] parameters)
            {
              var conn = context.Database.GetDbConnection();
              using (var command = conn.CreateCommand())
              {
                command.CommandText = rawSql;
                if (parameters != null)
                  foreach (var p in parameters)
                    command.Parameters.Add(p);
                await conn.OpenAsync();
                return (T)await command.ExecuteScalarAsync();
              }
            }
          }
          

          【讨论】:

            【解决方案11】:

            你可以使用这个(来自https://github.com/aspnet/EntityFrameworkCore/issues/1862#issuecomment-451671168):

            public static class SqlQueryExtensions
            {
                public static IList<T> SqlQuery<T>(this DbContext db, string sql, params object[] parameters) where T : class
                {
                    using (var db2 = new ContextForQueryType<T>(db.Database.GetDbConnection()))
                    {
                        // share the current database transaction, if one exists
                        var transaction = db.CurrentTransaction;
                        if (transaction != null)
                            db2.Database.UseTransaction(transaction.GetDbTransaction());
                        return db2.Query<T>().FromSql(sql, parameters).ToList();
                    }
                }
            
                public static IList<T> SqlQuery<T>(this DbContext db, Func<T> anonType, string sql, params object[] parameters) where T : class
                    => SqlQuery<T>(db, sql, parameters);
            
                private class ContextForQueryType<T> : DbContext where T : class
                {
                    private readonly DbConnection connection;
            
                    public ContextForQueryType(DbConnection connection)
                    {
                        this.connection = connection;
                    }
            
                    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
                    {
                        // switch on the connection type name to enable support multiple providers
                        // var name = con.GetType().Name;
                        optionsBuilder.UseSqlServer(connection, options => options.EnableRetryOnFailure());
            
                        base.OnConfiguring(optionsBuilder);
                    }
            
                    protected override void OnModelCreating(ModelBuilder modelBuilder)
                    {
                        modelBuilder.Entity<T>().HasNoKey();
                        base.OnModelCreating(modelBuilder);
                    }
                }
            }
            

            及用法:

                using (var db = new Db())
                {
                    var results = db.SqlQuery<ArbitraryType>("select 1 id, 'joe' name");
                    //or with an anonymous type like this
                    var results2 = db.SqlQuery(() => new { id =1, name=""},"select 1 id, 'joe' name");
                }
            

            【讨论】:

            • modelBulider.Query&lt;T&gt;() 在 EntityFramework Core 3 中已弃用
            • 为 EF Core 3+ 更新
            • 匿名查询db.SqlQuery(() =&gt; new { id =1, name=""},"select 1 id, 'joe' name"); 抛出编译错误Type arguments for method SqlQuery&lt;T&gt; cannot inferred from usage。如何将它与匿名类型一起使用。 .NET 5 中也不存在查询。github 链接包含固定代码
            • 我相信@ErikEJ 所指的更新可以在这里找到:github.com/dotnet/efcore/issues/1862#issuecomment-597022290 顺便说一句,这正是我想要的,谢谢!我尝试了建议的答案,但截至 2021 年 5 月,它仍在尝试创建模型。不知道为什么 EF 团队在没有类似替代品的情况下删除了 db.Database.SqlQuery() 超出了我的范围!
            • @AnthonyGriggs,我认为 EF Core 和 .NET Core 还没有准备好使用我所掌握的内容。我越深入研究它,几乎所有东西都丢失了!如果他们知道自己无法重现良好的旧 .NET,他们应该继续维护 .NET 和 EF。大多数函数/方法到处都丢失了,有很多借口可以解释它们丢失的原因!
            【解决方案12】:

            使用 Entity Framework 6,您可以执行如下操作

            创建模态类

            Public class User
            {
                    public int Id { get; set; }
                    public string fname { get; set; }
                    public string lname { get; set; }
                    public string username { get; set; }
            }
            

            如下执行 Raw DQL SQL 命令:

            var userList = datacontext.Database.SqlQuery<User>(@"SELECT u.Id ,fname , lname ,username FROM dbo.Users").ToList<User>();
            

            【讨论】:

              【解决方案13】:

              试试这个:(创建扩展方法)

              public static List<T> ExecuteQuery<T>(this dbContext db, string query) where T : class, new()
                      {
                          using (var command = db.Database.GetDbConnection().CreateCommand())
                          {
                              command.CommandText = query;
                              command.CommandType = CommandType.Text;
              
                              db.Database.OpenConnection();
              
                              using (var reader = command.ExecuteReader())
                              {
                                  var lst = new List<T>();
                                  var lstColumns = new T().GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
                                  while (reader.Read())
                                  {
                                      var newObject = new T();
                                      for (var i = 0; i < reader.FieldCount; i++)
                                      {
                                          var name = reader.GetName(i);
                                          PropertyInfo prop = lstColumns.FirstOrDefault(a => a.Name.ToLower().Equals(name.ToLower()));
                                          if (prop == null)
                                          {
                                              continue;
                                          }
                                          var val = reader.IsDBNull(i) ? null : reader[i];
                                          prop.SetValue(newObject, val, null);
                                      }
                                      lst.Add(newObject);
                                  }
              
                                  return lst;
                              }
                          }
                      }
              

              用法:

              var db = new dbContext();
              string query = @"select ID , Name from People where ... ";
              var lst = db.ExecuteQuery<PeopleView>(query);
              

              我的模型:(不在DbSet):

              public class PeopleView
              {
                  public int ID { get; set; }
                  public string Name { get; set; }
              }
              

              .netCore 2.2 and 3.0 中测试。

              注意:此方案性能较慢

              【讨论】:

              • 尝试仅按名称搜索 PropertyInfo 一次,仅用于第一条记录,并按列索引构建 PropertyInfo[] 数组以用于下一条记录。
              • @AminRostami 干得好
              【解决方案14】:

              我的案例使用存储过程而不是原始 SQL

              创建了一个类

              Public class School
              {
                  [Key]
                  public Guid SchoolId { get; set; }
                  public string Name { get; set; }
                  public string Branch { get; set; }
                  public int NumberOfStudents  { get; set; }
              }
              

              在我的DbContext 类下面添加

              public DbSet<School> SP_Schools { get; set; }
              

              执行存储过程:

              var MySchools = _db.SP_Schools.FromSqlRaw("GetSchools @schoolId, @page, @size ",
                            new SqlParameter("schoolId", schoolId),
                            new SqlParameter("page", page),
                            new SqlParameter("size", size)))
              .IgnoreQueryFilters();
              

              【讨论】:

                【解决方案15】:

                我知道这是一个老问题,但也许它可以帮助某人在不将 DTO 添加为 DbSet 的情况下调用存储过程。

                https://*.com/a/62058345/3300944

                【讨论】:

                  【解决方案16】:

                  这个解决方案很大程度上依赖于@pius 的解决方案。我想添加支持查询参数的选项以帮助缓解 SQL 注入,并且我还想使其成为 Entity Framework Core 的 DbContext DatabaseFacade 的扩展,使其更加集成。

                  首先创建一个带有扩展名的新类:

                  using Microsoft.EntityFrameworkCore;
                  using Microsoft.EntityFrameworkCore.Infrastructure;
                  using Microsoft.EntityFrameworkCore.Metadata;
                  using System;
                  using System.Collections.Generic;
                  using System.Data;
                  using System.Data.Common;
                  using System.Linq;
                  using System.Threading.Tasks;
                  
                  namespace EF.Extend
                  {
                  
                      public static class ExecuteSqlExt
                      {
                          /// <summary>
                          /// Execute raw SQL query with query parameters
                          /// </summary>
                          /// <typeparam name="T">the return type</typeparam>
                          /// <param name="db">the database context database, usually _context.Database</param>
                          /// <param name="query">the query string</param>
                          /// <param name="map">the map to map the result to the object of type T</param>
                          /// <param name="queryParameters">the collection of query parameters, if any</param>
                          /// <returns></returns>
                          public static List<T> ExecuteSqlRawExt<T, P>(this DatabaseFacade db, string query, Func<DbDataReader, T> map, IEnumerable<P> queryParameters = null)
                          {
                              using (var command = db.GetDbConnection().CreateCommand())
                              {
                                  if((queryParameters?.Any() ?? false))
                                      command.Parameters.AddRange(queryParameters.ToArray());
                  
                                  command.CommandText = query;
                                  command.CommandType = CommandType.Text;
                  
                                  db.OpenConnection();
                  
                                  using (var result = command.ExecuteReader())
                                  {
                                      var entities = new List<T>();
                  
                                      while (result.Read())
                                      {
                                          entities.Add(map(result));
                                      }
                  
                                      return entities;
                                  }
                              }
                                  
                          }
                      }
                  
                  }
                  

                  请注意,上面的“T”是返回的类型,“P”是查询参数的类型,这将根据您使用的是 MySql、Sql 等而有所不同。

                  接下来我们将展示一个示例。我正在使用 MySql EF Core 功能,因此我们将了解如何将上面的通用扩展与这个更具体的 MySql 实现一起使用:

                  //add your using statement for the extension at the top of your Controller
                  //with all your other using statements
                  using EF.Extend;
                  
                  //then your your Controller looks something like this
                  namespace Car.Api.Controllers
                  {
                  
                      //Define a quick Car class for the custom return type
                      //you would want to put this in it's own class file probably
                      public class Car
                      {
                          public string Make { get; set; }
                          public string Model { get; set; }
                          public string DisplayTitle { get; set; }
                      }
                  
                      [ApiController]
                      public class CarController : ControllerBase
                      {
                          private readonly ILogger<CarController> _logger;
                          //this would be your Entity Framework Core context
                          private readonly CarContext _context;
                  
                          public CarController(ILogger<CarController> logger, CarContext context)
                          {
                              _logger = logger;
                              _context = context;
                          }
                  
                          //... more stuff here ...
                  
                         /// <summary>
                         /// Get car example
                         /// </summary>
                         [HttpGet]
                         public IEnumerable<Car> Get()
                         {
                             //instantiate three query parameters to pass with the query
                             //note the MySqlParameter type is because I'm using MySql
                             MySqlParameter p1 = new MySqlParameter
                             {
                                 ParameterName = "id1",
                                 Value = "25"
                             };
                  
                             MySqlParameter p2 = new MySqlParameter
                             {
                                 ParameterName = "id2",
                                 Value = "26"
                             };
                  
                             MySqlParameter p3 = new MySqlParameter
                             {
                                 ParameterName = "id3",
                                 Value = "27"
                             };
                  
                             //add the 3 query parameters to an IEnumerable compatible list object
                             List<MySqlParameter> queryParameters = new List<MySqlParameter>() { p1, p2, p3 };
                  
                             //note the extension is now easily accessed off the _context.Database object
                             //also note for ExecuteSqlRawExt<Car, MySqlParameter>
                             //Car is my return type "T"
                             //MySqlParameter is the specific DbParameter type MySqlParameter type "P"
                             List<Car> result = _context.Database.ExecuteSqlRawExt<Car, MySqlParameter>(
                          "SELECT Car.Make, Car.Model, CONCAT_WS('', Car.Make, ' ', Car.Model) As DisplayTitle FROM Car WHERE Car.Id IN(@id1, @id2, @id3)",
                          x => new Car { Make = (string)x[0], Model = (string)x[1], DisplayTitle = (string)x[2] }, 
                          queryParameters);
                  
                             return result;
                         }
                      }
                  }
                  

                  查询将返回如下行:
                  “福特”、“探险者”、“福特探险者”
                  “特斯拉”、“X 型”、“特斯拉 X 型”

                  显示标题未定义为数据库列,因此默认情况下它不会成为 EF Car 模型的一部分。我喜欢这种方法作为许多可能的解决方案之一。此页面上的其他答案引用了使用 [NotMapped] 装饰器解决此问题的其他方法,根据您的用例,这可能是更合适的方法。

                  请注意,此示例中的代码显然比它需要的更冗长,但我认为它使示例更清晰。

                  【讨论】:

                  • 谢谢丹尼尔。我还需要为“Microsoft.EntityFrameworkCore.Relational 添加包
                  【解决方案17】:

                  实际上,您可以创建一个通用存储库并执行类似的操作

                  public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : BaseEntity
                  {
                      private readonly DataContext context;
                      private readonly DbSet<TEntity> dbSet;
                  
                      public GenericRepository(DataContext context)
                      {
                          this.context = context;
                          this.dbSet = context.Set<TEntity>();
                      }
                  
                     
                      public IEnumerable<TEntity> ExecuteCommandQuery(string command)
                          => dbSet.FromSqlRaw(command);
                  
                  }
                  

                  【讨论】:

                  • 补充:Microsoft.EntityFrameworkCore 不包含 FromSqlRaw。必须安装 Microsoft.EntityFrameworkCore.Relational 才能使用此方法。
                  • efcore5.0 不可能,因为必须先注册 TEntity。 "Cannot create a DbSet for 'DatabaseFirewallRuleModel' because this type is not included in the model for the context."
                  【解决方案18】:

                  我从 @AminRostami 更新了扩展方法以返回 IAsyncEnumerable(因此可以应用 LINQ 过滤),并且它将从数据库返回的记录的模型列名称映射到模型(使用 EF Core 5 测试):

                  扩展本身:

                  public static class QueryHelper
                  {
                      private static string GetColumnName(this MemberInfo info)
                      {
                          List<ColumnAttribute> list = info.GetCustomAttributes<ColumnAttribute>().ToList();
                          return list.Count > 0 ? list.Single().Name : info.Name;
                      }
                      /// <summary>
                      /// Executes raw query with parameters and maps returned values to column property names of Model provided.
                      /// Not all properties are required to be present in model (if not present - null)
                      /// </summary>
                      public static async IAsyncEnumerable<T> ExecuteQuery<T>(
                          [NotNull] this DbContext db,
                          [NotNull] string query,
                          [NotNull] params SqlParameter[] parameters)
                          where T : class, new()
                      {
                          await using DbCommand command = db.Database.GetDbConnection().CreateCommand();
                          command.CommandText = query;
                          command.CommandType = CommandType.Text;
                          if (parameters != null)
                          {
                              foreach (SqlParameter parameter in parameters)
                              {
                                  command.Parameters.Add(parameter);
                              }
                          }
                          await db.Database.OpenConnectionAsync();
                          await using DbDataReader reader = await command.ExecuteReaderAsync();
                          List<PropertyInfo> lstColumns = new T().GetType()
                              .GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
                          while (await reader.ReadAsync())
                          {
                              T newObject = new();
                              for (int i = 0; i < reader.FieldCount; i++)
                              {
                                  string name = reader.GetName(i);
                                  PropertyInfo prop = lstColumns.FirstOrDefault(a => a.GetColumnName().Equals(name));
                                  if (prop == null)
                                  {
                                      continue;
                                  }
                                  object val = await reader.IsDBNullAsync(i) ? null : reader[i];
                                  prop.SetValue(newObject, val, null);
                              }
                              yield return newObject;
                          }
                      }
                  }
                  

                  使用的模型(注意列名与实际属性名不同):

                  public class School
                  {
                      [Key] [Column("SCHOOL_ID")] public int SchoolId { get; set; }
                  
                      [Column("CLOSE_DATE", TypeName = "datetime")]
                      public DateTime? CloseDate { get; set; }
                  
                      [Column("SCHOOL_ACTIVE")] public bool? SchoolActive { get; set; }
                  }
                  

                  实际使用情况:

                  public async Task<School> ActivateSchool(int schoolId)
                  {
                      // note that we're intentionally not returning "SCHOOL_ACTIVE" with select statement
                      // this might be because of certain IF condition where we return some other data
                      return await _context.ExecuteQuery<School>(
                          "UPDATE SCHOOL SET SCHOOL_ACTIVE = 1 WHERE SCHOOL_ID = @SchoolId; SELECT SCHOOL_ID, CLOSE_DATE FROM SCHOOL",
                          new SqlParameter("@SchoolId", schoolId)
                      ).SingleAsync();
                  }
                  

                  【讨论】:

                  • 我不得不说这是最有用的答案。然而,它并不像看起来那么简单,并且存在许多边缘情况问题(例如处理可为空的类型、事务、字符串、DateTime、Guid、十进制类型……)。我有处理所有这些的代码(基于上面的代码),当我有空闲时间时,我会在这里发布。
                  • 就是这样!不需要 DbSet,防止 SQL 注入,自动映射到目标类 (T)。非常感谢。
                  • @KonstantinKonstantinov 您介意发布您的更新吗?
                  • @SeriousM - 我刚刚发布了代码。
                  【解决方案19】:

                  我之所以提出这个问题,是因为我们在 Entity Framework 6 中有超过 100 个无实体使用 SqlQuery 的实例,因此在我们的案例中,按照 Microsoft 建议的方式根本无法轻松工作。

                  此外,在从EF 迁移到EFC 的过程中,我们必须将单个EF(Entity Framework 6)/EFC(Entity Framework Core 5)代码库维护几个月。代码库相当大,根本不可能“一夜之间”迁移。

                  下面的答案是基于上面的很好的答案,它只是一个小的扩展,使它们适用于更多的边缘情况。

                  首先,对于每个基于EF 的项目,我们创建了一个基于EFC 的项目(例如MyProject.csproj ==> MyProject_EFC.csproj)并在所有此类EFC 项目中定义了一个常量EFCORE。如果您正在执行从EFEFC 的快速一次性迁移,那么您不需要这样做,您可以保留#if EFCORE ... #else 中的内容并删除下面#else ... #endif 中的内容。

                  这是主要的互操作扩展类。

                  using System;
                  using System.Collections.Generic;
                  using System.Threading;
                  
                  #if EFCORE
                  using System.ComponentModel.DataAnnotations.Schema;
                  using System.Data;
                  using System.Data.Common;
                  using System.Linq;
                  using System.Reflection;
                  using System.Threading.Tasks;
                  using Microsoft.EntityFrameworkCore;
                  using Microsoft.EntityFrameworkCore.ChangeTracking;
                  using Microsoft.EntityFrameworkCore.Storage;
                  using Database = Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade;
                  using MoreLinq.Extensions;
                  #else
                  using System.Data.Entity;
                  using System.Data.Entity.Infrastructure;
                  #endif
                  
                  namespace YourNameSpace.EntityFrameworkCore
                  {
                      /// <summary>
                      /// Collection of extension methods to simplify migration from EF to EFC.
                      /// </summary>
                      public static class EntityFrameworkCoreInterop
                      {
                          /// <summary>
                          /// https://*.com/questions/6637679/reflection-get-attribute-name-and-value-on-property
                          /// </summary>
                          public static TAttribute? TryGetAttribute<TAttribute>(this PropertyInfo prop) where TAttribute : Attribute =>
                              prop.GetCustomAttributes(true).TryGetAttribute<TAttribute>();
                  
                          public static TAttribute? TryGetAttribute<TAttribute>(this Type t) where TAttribute : Attribute =>
                              t.GetCustomAttributes(true).TryGetAttribute<TAttribute>();
                  
                          public static TAttribute? TryGetAttribute<TAttribute>(this IEnumerable<object> attrs) where TAttribute : Attribute
                          {
                              foreach (object attr in attrs)
                              {
                                  switch (attr)
                                  {
                                      case TAttribute t:
                                      {
                                          return t;
                                      }
                                  }
                              }
                  
                              return null;
                          }
                  
                          /// <summary>
                          /// Returns true if the source string matches *any* of the passed-in strings (case insensitive)
                          /// </summary>
                          public static bool EqualsNoCase(this string? s, params string?[]? targets)
                          {
                              if (s == null && (targets == null || targets.Length == 0))
                              {
                                  return true;
                              }
                  
                              if (targets == null)
                              {
                                  return false;
                              }
                  
                              return targets.Any(t => string.Equals(s, t, StringComparison.OrdinalIgnoreCase));
                          }
                  
                  #if EFCORE
                          public class EntityException : Exception
                          {
                              public EntityException(string message) : base(message)
                              {
                              }
                          }
                  
                          public static TEntity GetEntity<TEntity>(this EntityEntry<TEntity> entityEntry)
                              where TEntity : class => entityEntry.Entity;
                  
                          #region SqlQuery Interop
                  
                          /// <summary>
                          /// kk:20210727 - This is a little bit ugly but given that this interop method is used just once,
                          /// it is not worth spending more time on it.
                          /// </summary>
                          public static List<T> ToList<T>(this IOrderedAsyncEnumerable<T> e) =>
                              Task.Run(() => e.ToListAsync().AsTask()).GetAwaiter().GetResult();
                  
                          private static string GetColumnName(this MemberInfo info) =>
                              info.GetCustomAttributes().TryGetAttribute<ColumnAttribute>()?.Name ?? info.Name;
                  
                          /// <summary>
                          /// See: https://*.com/questions/35631903/raw-sql-query-without-dbset-entity-framework-core
                          /// Executes raw query with parameters and maps returned values to column property names of Model provided.
                          /// Not all properties are required to be present in the model. If not present then they will be set to nulls.
                          /// </summary>
                          private static async IAsyncEnumerable<T> ExecuteQuery<T>(this Database database, string query, params object[] parameters)
                          {
                              await using DbCommand command = database.GetDbConnection().CreateCommand();
                              command.CommandText = query;
                              command.CommandType = CommandType.Text;
                  
                              if (database.CurrentTransaction != null)
                              {
                                  command.Transaction = database.CurrentTransaction.GetDbTransaction();
                              }
                  
                              foreach (var parameter in parameters)
                              {
                                  // They are supposed to be of SqlParameter type but are passed as objects.
                                  command.Parameters.Add(parameter);
                              }
                  
                              await database.OpenConnectionAsync();
                              await using DbDataReader reader = await command.ExecuteReaderAsync();
                              var t = typeof(T);
                  
                              // TODO kk:20210825 - I do know that the code below works as we use it in some other place where it does work.
                              // However, I am not 100% sure that R# proposed version does. Check and refactor when time permits.
                              //
                              // ReSharper disable once CheckForReferenceEqualityInstead.1
                              if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                              {
                                  t = Nullable.GetUnderlyingType(t)!;
                              }
                  
                              var lstColumns = t
                                  .GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                                  .ToList();
                  
                              while (await reader.ReadAsync())
                              {
                                  if (t.IsPrimitive || t == typeof(string) || t == typeof(DateTime) || t == typeof(Guid) || t == typeof(decimal))
                                  {
                                      var val = await reader.IsDBNullAsync(0) ? null : reader[0];
                                      yield return (T) val!;
                                  }
                                  else
                                  {
                                      var newObject = Activator.CreateInstance<T>();
                  
                                      for (var i = 0; i < reader.FieldCount; i++)
                                      {
                                          var name = reader.GetName(i);
                                          var val = await reader.IsDBNullAsync(i) ? null : reader[i];
                                          var prop = lstColumns.FirstOrDefault(a => a.GetColumnName().EqualsNoCase(name));
                  
                                          if (prop == null)
                                          {
                                              continue;
                                          }
                  
                                          prop.SetValue(newObject, val, null);
                                      }
                  
                                      yield return newObject;
                                  }
                              }
                          }
                  
                          #endregion
                  
                          public static DbRawSqlQuery<TElement> SqlQuery<TElement>(this Database database, string sql, params object[] parameters) =>
                              new(database, sql, parameters);
                  
                          public class DbRawSqlQuery<TElement> : IAsyncEnumerable<TElement>
                          {
                              private readonly IAsyncEnumerable<TElement> _elements;
                  
                              internal DbRawSqlQuery(Database database, string sql, params object[] parameters) =>
                                  _elements = ExecuteQuery<TElement>(database, sql, parameters);
                  
                              public IAsyncEnumerator<TElement> GetAsyncEnumerator(CancellationToken cancellationToken = new ()) =>
                                  _elements.GetAsyncEnumerator(cancellationToken);
                  
                              public async Task<TElement> SingleAsync() => await _elements.SingleAsync();
                              public TElement Single() => Task.Run(SingleAsync).GetAwaiter().GetResult();
                              public async Task<TElement> FirstAsync() => await _elements.FirstAsync();
                              public TElement First() => Task.Run(FirstAsync).GetAwaiter().GetResult();
                              public async Task<TElement?> SingleOrDefaultAsync() => await _elements.SingleOrDefaultAsync();
                              public async Task<int> CountAsync() => await _elements.CountAsync();
                              public async Task<List<TElement>> ToListAsync() => await _elements.ToListAsync();
                              public List<TElement> ToList() => Task.Run(ToListAsync).GetAwaiter().GetResult();
                  
                          }
                  #endif
                      }
                  }
                  
                  

                  并且用法与以前的 EF 用法没有区别:

                  public async Task<List<int>> GetMyResults()
                  {
                      using var ctx = GetMyDbContext();
                      const string sql = "select 1 as Result";
                      return await ctx.GetDatabase().SqlQuery<int>(sql).ToListAsync();
                  }
                  

                  其中GetMyDbContext 是一种获取数据库上下文的方法,GetDatabase 是一个单行互操作,它为给定的IMyDbContext : DbContext 返回((DbContext)context).Database。这是为了简化同时进行的EF / EFC 操作。

                  这适用于原始类型(上面的示例)、实体、本地类(但不适用于匿名类)。通过GetColumnName 支持列重命名,但是,...上面已经完成了。

                  【讨论】:

                    【解决方案20】:

                    用于查询数据:没有现有实体

                                   string query = "SELECT r.Name as roleName, ur.roleId, u.Id as userId   FROM dbo.AspNetUserRoles AS ur INNER JOIN dbo.AspNetUsers AS u ON ur.UserId = u.Id INNER JOIN dbo.AspNetRoles AS r ON ur.RoleId = r.Id ";
                    
                                   ICollection<object> usersWithRoles = new List<object>();
                                    using (var command = _identityDBContext.Database.GetDbConnection().CreateCommand())
                                    {
                                        command.CommandText = query;
                                        command.CommandType = CommandType.Text;
                    
                                        await _identityDBContext.Database.OpenConnectionAsync();
                    
                                        using (var reader = await command.ExecuteReaderAsync())
                                        {      
                                            while (await reader.ReadAsync())
                                            {
                                                usersWithRoles.Add(new { 
                                                    roleName = reader.GetFieldValueAsync<string>(0).Result, 
                                                    roleId = reader.GetFieldValueAsync<string>(1).Result,
                                                    userId = reader.GetFieldValueAsync<string>(2).Result
                                                });
                                            }    
                                        }
                                    }
                    

                    详细说明:

                     [HttpGet]
                        [Route("GetAllUsersWithRoles")]
                        public async Task<IActionResult> GetAllUsersWithRoles()
                        {
                            string query = "SELECT r.Name as roleName, ur.roleId, u.Id as userId   FROM dbo.AspNetUserRoles AS ur INNER JOIN dbo.AspNetUsers AS u ON ur.UserId = u.Id INNER JOIN dbo.AspNetRoles AS r ON ur.RoleId = r.Id ";
                            try
                            {
                                ICollection<object> usersWithRoles = new List<object>();
                                using (var command = _identityDBContext.Database.GetDbConnection().CreateCommand())
                                {
                                    command.CommandText = query;
                                    command.CommandType = CommandType.Text;
                    
                                    await _identityDBContext.Database.OpenConnectionAsync();
                    
                                    using (var reader = await command.ExecuteReaderAsync())
                                    {      
                                        while (await reader.ReadAsync())
                                        {
                                            usersWithRoles.Add(new { 
                                                roleName = reader.GetFieldValueAsync<string>(0).Result, 
                                                roleId = reader.GetFieldValueAsync<string>(1).Result,
                                                userId = reader.GetFieldValueAsync<string>(2).Result
                                            });
                                        }    
                                    }
                                }
                                    return StatusCode(200, usersWithRoles); // Get all users   
                            }
                            catch (Exception e)
                            {
                                return StatusCode(500, e);
                            }
                        }
                    

                    结果如下所示:

                    [
                      {
                        "roleName": "admin",
                        "roleId": "7c9cb1be-e987-4ec1-ae4d-e4c9790f57d8",
                        "userId": "12eadc86-6311-4d5e-8be8-df30799df265"
                      },
                      {
                        "roleName": "user",
                        "roleId": "a0d5ef46-b1e6-4a53-91ce-9ff5959f1ed8",
                        "userId": "12eadc86-6311-4d5e-8be8-df30799df265"
                      },
                      {
                        "roleName": "user",
                        "roleId": "a0d5ef46-b1e6-4a53-91ce-9ff5959f1ed8",
                        "userId": "3e7cd970-8c52-4dd1-847c-f824671ea15d"
                      }
                    ]
                    

                    【讨论】:

                      【解决方案21】:

                      为 Entity Framework Core 5 完成此操作,需要安装

                      Microsoft.EntityFrameworkCore.Relational
                      

                      辅助扩展方法

                      using Microsoft.EntityFrameworkCore;
                      using Microsoft.EntityFrameworkCore.Infrastructure;
                      using Microsoft.EntityFrameworkCore.Storage;
                      using System;
                      using System.Collections.Generic;
                      using System.Data;
                      using System.Data.Common;
                      using System.Linq;
                      using System.Linq.Expressions;
                      using System.Reflection;
                      
                      
                      public static class EfHelper
                      {
                          public static DbTransaction GetDbTransaction(this IDbContextTransaction source)
                          {
                              return (source as IInfrastructure<DbTransaction>).Instance;
                          }
                      
                          private class PropertyMapp
                          {
                              public string Name { get; set; }
                              public Type Type { get; set; }
                      
                              public bool IsSame(PropertyMapp mapp)
                              {
                                  if (mapp == null)
                                  {
                                      return false;
                                  }
                                  bool same = mapp.Name == Name && mapp.Type == Type;
                                  return same;
                              }
                          }
                      
                          public static IEnumerable<T> FromSqlQuery<T>(this DbContext context, string query, params object[] parameters) where T : new()
                          {
                              const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
                              List<PropertyMapp> entityFields = (from PropertyInfo aProp in typeof(T).GetProperties(flags)
                                                                 select new PropertyMapp
                                                                 {
                                                                     Name = aProp.Name,
                                                                     Type = Nullable.GetUnderlyingType(aProp.PropertyType) ?? aProp.PropertyType
                                                                 }).ToList();
                              List<PropertyMapp> dbDataReaderFields = new List<PropertyMapp>();
                              List<PropertyMapp> commonFields = null;
                      
                              using (var command = context.Database.GetDbConnection().CreateCommand())
                              {
                                  if (command.Connection.State != ConnectionState.Open)
                                  {
                                      command.Connection.Open();
                                  }
                                  var currentTransaction = context.Database.CurrentTransaction;
                                  if (currentTransaction != null)
                                  {
                                      command.Transaction = currentTransaction.GetDbTransaction();
                                  }
                                  command.CommandText = query;
                                  if (parameters.Any())
                                  {
                                      command.Parameters.AddRange(parameters);
                                  }
                                  using (var result = command.ExecuteReader())
                                  {
                                      while (result.Read())
                                      {
                                          if (commonFields == null)
                                          {
                                              for (int i = 0; i < result.FieldCount; i++)
                                              {
                                                  dbDataReaderFields.Add(new PropertyMapp { Name = result.GetName(i), Type = result.GetFieldType(i) });
                                              }
                                              commonFields = entityFields.Where(x => dbDataReaderFields.Any(d => d.IsSame(x))).Select(x => x).ToList();
                                          }
                      
                                          var entity = new T();
                                          foreach (var aField in commonFields)
                                          {
                                              PropertyInfo propertyInfos = entity.GetType().GetProperty(aField.Name);
                                              var value = (result[aField.Name] == DBNull.Value) ? null : result[aField.Name]; //if field is nullable
                                              propertyInfos.SetValue(entity, value, null);
                                          }
                                          yield return entity;
                                      }
                                  }
                              }
                          }
                      
                          /*
                           * https://entityframeworkcore.com/knowledge-base/35631903/raw-sql-query-without-dbset---entity-framework-core
                           */
                          public static IEnumerable<T> FromSqlQuery<T>(this DbContext context, string query, Func<DbDataReader, T> map, params object[] parameters)
                          {
                              using (var command = context.Database.GetDbConnection().CreateCommand())
                              {
                                  if (command.Connection.State != ConnectionState.Open)
                                  {
                                      command.Connection.Open();
                                  }
                                  var currentTransaction = context.Database.CurrentTransaction;
                                  if (currentTransaction != null)
                                  {
                                      command.Transaction = currentTransaction.GetDbTransaction();
                                  }
                                  command.CommandText = query;
                                  if (parameters.Any())
                                  {
                                      command.Parameters.AddRange(parameters);
                                  }
                                  using (var result = command.ExecuteReader())
                                  {
                                      while (result.Read())
                                      {
                                          yield return map(result);
                                      }
                                  }
                              }
                          }
                      }
                      

                      型号

                      public class UserModel
                      {
                          public string Name { get; set; }
                          public string Email { get; set; }
                          public bool? IsDeleted { get; set; }
                      }
                      

                      手动映射

                      List<UserModel> usersInDb = Db.FromSqlQuery
                      (
                          "SELECT Name, Email FROM Users WHERE Name=@paramName",
                          x => new UserModel 
                          { 
                              Name = (string)x[0], 
                              Email = (string)x[1] 
                          },
                          new SqlParameter("@paramName", user.Name)
                      )
                      .ToList();
                      
                      usersInDb = Db.FromSqlQuery
                      (
                          "SELECT Name, Email FROM Users WHERE Name=@paramName",
                          x => new UserModel 
                          { 
                              Name = x["Name"] is DBNull ? "" : (string)x["Name"], 
                              Email = x["Email"] is DBNull ? "" : (string)x["Email"] 
                          },
                          new SqlParameter("@paramName", user.Name)
                      )
                      .ToList();
                      

                      使用反射的自动映射

                      List<UserModel> usersInDb = Db.FromSqlQuery<UserModel>
                      (
                          "SELECT Name, Email, IsDeleted FROM Users WHERE Name=@paramName",
                          new SqlParameter("@paramName", user.Name)
                      )
                      .ToList();
                      

                      【讨论】: