没有DbSet的原始SQL查询-实体框架核心


107

删除实体框架核心 dbData.Database.SqlQuery<SomeModel>我找不到为我的全文搜索查询构建原始SQL查询的解决方案,该查询将返回表数据以及排名。

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

有任何想法吗???


15
当我真的只需要针对特定​​用例的简单DTO时,我将非常想念SqlQuery <T>,并且不想将自定义类映射到我的DbContext。我创建了一个用户的声音,要求加入到EF核心这个功能回到任何人都可以投票,如果他们希望这个功能回到:data.uservoice.com/forums/...
马特·桑德斯

1
根据github.com/aspnet/EntityFramework/issues/1862,此目标现在针对EF核心1.2和/或1.1.0-preview1
Dan Field,

2
以@Devon所说的为基础,我花了很长时间才弄清楚它们是Microsoft.EntityFrameworkCore.SqlServer中的扩展方法。您需要在获取这些扩展方法之前将其添加到您的项目中。
丹尼尔(Daniel)

3
感叹这似乎是某种建筑宇航员的决定:“人们不需要这个”。我想我只需要为这种情况安装Dapper。烦死了
Dirk Boer

1
@MattSanders-您的uservoice链接同时似乎已消失。你知道它去了哪里吗?
Dirk Boer

Answers:


126

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

如果您使用的是EF Core 2.1

如果您使用的是2018年5月7日之后可用的EF Core 2.1 Release Candidate 1,则可以利用建议的新功能(查询类型)。

什么是查询类型

除实体类型外,EF Core模型还可包含查询类型,该查询类型可用于对未映射到实体类型的数据执行数据库查询。

什么时候使用查询类型?

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

映射到数据库视图。

映射到未定义主键的表。

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

因此,您不再需要进行建议作为问题答案的所有技巧或变通办法。只需按照以下步骤操作:

首先,您定义一个新的type属性,DbQuery<T>其中Ttype是将携带SQL查询的列值的类的类型。因此,您DbContext将拥有以下功能:

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

其次,使用FromSql与您相同的方法DbSet<T>

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

另请注意,DdContexts是部分类,因此您可以创建一个或多个单独的文件来组织“ raw SQL DbQuery”定义,以最适合您。


如果您使用的是EF Core 3.0和更高版本

查询类型现在称为无键实体类型。如上所述,EF Core 2.1中引入了查询类型。如果您使用的是EF Core 3.0或更高版本,则现在应该考虑使用无密钥完整性类型,因为查询类型现已标记为过时。

此功能已在EF Core 2.1中的查询类型名称下添加。在EF Core 3.0中,该概念已重命名为无密钥实体类型。[无密钥]数据注释在EFCore 5.0中可用。

对于何时使用无密钥实体类型,我们仍然具有与查询类型相同的方案。

因此,要使用它,您需要首先SomeModel使用[Keyless]数据注释或通过.HasNoKey()如下所示的方法调用进行流畅的配置来标记您的类:

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

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<SomeModel>().HasNoKey();
}

配置完成后,您可以使用此处介绍的方法之一执行SQL查询。例如,您可以使用以下代码:

var result = context.SomeModels.FromSqlRaw("SQL SCRIPT").ToList();

18
使用EF Core 2.1及更高版本时,此答案应该是最佳解决方案。👍
威尔黄

2
@CodeNotFound如果我不需要结果或它是原始类型(例如bit),该怎么办?
Shimmy Weitzhandler

5
使用CodeFirst会自动创建一个具有所有这些属性的表,将其添加[NotMapped]SomeModels类中对我不起作用。我有想念吗?
Jean-Paul

7
EF Core 3.0不赞成使用DbQueryDbSet无键实体类型一起使用
NetMage

3
仅供参考,由于EF core 3.0中的某些错误,即使在标记有HasNoKey()的实体上,代码优先迁移仍将尝试创建表。因此,您还必须添加.ToView(null)。例如modelBuilder.Entity<MyData>().HasNoKey().ToView(null);@ Jean-Paul我认为这可以解决您的问题
stann1

36

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

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 的声明,这是2.0版之后的重中之重。这个问题正在跟踪这里


不错的答案,喜欢它。
sebu

31

在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()

更多信息可以在这里找到


3
所以我想我也将不得不扩展它DbContext以包括一个新属性DbSet<Rank> Rank { get; set; }。现在,这对linq有什么影响?也就是说,现在我们不能使用类似的语句DBContext.Rank.Where(i => i.key == 1),并且该语句不会在SQL中没有实现,因此会失败吗?
David Harlow

针对此集合发出的Linq必须在内存中解析。如果需要发出不同的WHERE sql子句,则必须将它们作为参数包括在内或构建不同的脚本。
E-Bat

我的DbSet没有“ FromSql”方法。这是我缺少的扩展名吗?
birwin '17

1
@birwin,您需要导入命名空间Microsoft.EntityFrameworkCore
E-Bat

20

您可以在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]);
    }
}

18

现在,在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注入。

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

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

一些链接:

使用Entity Framework Core执行原始SQL查询

原始SQL查询


7

在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;
}

这样,将不会在数据库中创建Ranks模型。

现在,在您的控制器/操作中,您可以调用:

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

这样,您可以调用Raw SQL Procedures。


FromSqlPARAMS可以简单地通过,而无需创建SqlParameter对象:FromSql($"STORED_PROCEDURE {value1}, {value2}")或者FromSql("STORED_PROCEDURE {0}, {1}", value1, value2)(它们将被转义)。
马吉德

7

您可以使用它(来自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()))
        {
            return db2.Query<T>().FromSql(sql, parameters).ToList();
        }
    }

    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.Query<T>();
            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");
    }

6

添加Nuget包-Microsoft.EntityFrameworkCore.Relational

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

这会将行号作为int返回

请参阅-https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.executesqlcommand?view=efcore - 3.0


3
请注意,这只会返回受命令影响的行数:stackoverflow.com/a/49861799/299756
kalyfe

正是我所需要的。我正在使用Microsoft.EntityFrameworkCore 3.1.1,无法执行RAW查询和SP。非常感谢您!
jaysonragasa

5

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

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 []数组以用于下一条记录。
PetrVoborník

@AminRostami Nice Work
sebu

2

不是直接针对OP的场景,而是因为我一直在为此苦苦挣扎,所以我想删除这些ex。使使用以下命令更容易执行原始SQL的方法DbContext

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();
    }
  }
}

1

我使用Dapper绕过了实体框架核心的约束。

IDbConnection.Query

正在使用带有多个参数的sql查询或存储过程。顺便说一下,它要快一些(请参阅基准测试

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();
        }


        }
}

0

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


0

我的案例使用存储过程而不是原始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();


0

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

首先使用扩展名创建一个新类:

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;
       }
    }
}

该查询将返回以下行:
“ Ford”,“ Explorer”,“ Ford Explorer”
“ Tesla”,“ Model X”,“ Tesla Model X”

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

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


-6

使用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>();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.