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

C# 没有DbSet的原始SQL查询-实体框架核心,c#,entity-framework-core,C#,Entity Framework Core,由于Entity Framework Core正在删除dbData.Database.SqlQuery,我找不到为全文搜索查询构建原始SQL查询的解决方案,该查询将返回表数据和排名 我见过的在实体框架核心中构建原始SQL查询的唯一方法是通过dbData.Product.FromSql(“SQL脚本”)这是没有用的,因为我没有将映射我在查询中返回的排名的数据库集 有什么想法吗?在EF Core中,您不能再执行“免费”原始sql。您需要为该类定义POCO类和DbSet。 在您的情况下,您需要定义等级

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

我见过的在实体框架核心中构建原始SQL查询的唯一方法是通过
dbData.Product.FromSql(“SQL脚本”)这是没有用的,因为我没有将映射我在查询中返回的排名的数据库集


有什么想法吗?

在EF Core中,您不能再执行“免费”原始sql。您需要为该类定义POCO类和
DbSet
。 在您的情况下,您需要定义等级:

由于它肯定是只读的,因此包含
.AsNoTracking()
调用将非常有用

编辑-EF Core 3.0中的突破性更改:

var userList = datacontext.Database.SqlQuery<User>(@"SELECT u.Id ,fname , lname ,username FROM dbo.Users").ToList<User>();
DbQuery()现在已经过时,应该(再次)使用DbSet()。如果您有一个无键实体,即它不需要主键,则可以使用HasNoKey()方法:

ModelBuilder.Entity().HasNoKey()

可以找到更多信息

您可以在EF Core中执行原始sql-将此类添加到您的项目中。 这将允许您执行原始SQL并获得原始结果,而无需定义POCO和DBSet。 参见原始示例

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

现在,在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子句,则将忽略该子句

一些链接:


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

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

我计划在添加内置支持后立即将其删除。根据EF核心团队亚瑟·维克斯(Arthur Vickers)的一份报告,这是2.0后的优先事项。正在跟踪此问题。

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

如果您使用的是EF Core 2.1 如果您正在使用从2018年5月7日起提供的EF Core 2.1发行版候选1,您可以利用建议的新功能,即查询类型

是什么

除了实体类型外,EF核心模型还可以包含查询类型, 可用于对以下数据执行数据库查询: 未映射到实体类型

何时使用查询类型

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

映射到数据库视图

映射到未定义主键的表

映射到模型中定义的查询

因此,你不再需要做所有的黑客或变通方法,作为你问题的答案。只需遵循以下步骤:

首先,您定义了一个新属性,类型为
DbQuery
,其中
T
是将携带SQL查询的列值的类的类型。因此,在
DbContext
中,您将看到:

public DbQuery<SomeModel> SomeModels { get; set; }
还请注意,
DdContext
s是,因此您可以创建一个或多个单独的文件来组织“原始SQL数据库查询”定义,使其最适合您


如果您使用的是EF Core 3.0及更高版本 查询类型现在称为。如上所述,查询类型是在EF Core 2.1中引入的。如果使用EF核心3或更高版本,则现在应该考虑使用无键实体类型,因为查询类型现在被标记为过时。 此功能在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();
}

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

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
       modelBuilder.Query<Ranks>();
}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
modelBuilder.Query();
}
然后定义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;
}
公共异步任务GetRanks(字符串值1,可空值2)
{
SqlParameter value1Input=新的SqlParameter(“@Param1”,value1??(对象)DBNull.Value);
SqlParameter value2Input=新的SqlParameter(“@Param2”,value2??(对象)DBNull.Value);
List getRanks=wait this.Query();
返回等级;
}
这样,将不会在数据库中创建等级模型

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

List<Ranks> gettingRanks = _DbContext.GetRanks(value1,value2).Result.ToListAsync();
List gettingRanks=_DbContext.GetRanks(value1,value2.Result.toListSync();
通过这种方式,您可以调用原始SQL过程。

我曾经绕过实体框架核心的这个约束

IDbConnection.Query
正在使用sql查询或具有多个参数的存储过程。 顺便说一下,它的速度快了一点(参见)

简洁易学。编写和运行带有参数的存储过程需要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();
        }


        }
}
公共类podborsbyparameters服务
{
字符串_connectionString=null;
公共PodborsByParametersService(字符串连接)
{
这。_connectionString=connStr;
}
公共IList GetTyres(TyresPodborView pb、bool isPartner、string partnerId、int pointId)
{
字符串sqltext“spGetTyresPartnerToClient”;
var p=新的动态参数();
p、 添加(“@PartnerID”,PartnerID);
p、 添加(“@PartnerPointID”,pointId);
使用(IDbConnection db=newSQLConnection(_connectionString))
{
返回db.Query(sqltext,p,null,true,null,CommandType.StoredProcedure).ToList();
}
}
}
您也可以使用。就像整洁一样,这完全超出了EF的范畴。与Dapper(或EF)不同,您不需要维护POCO
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
       modelBuilder.Query<Ranks>();
}
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();
IDbConnection.Query
 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();
        }


        }
}
using Microsoft.EntityFrameworkCore;
...
await YourContext.Database.ExecuteSqlCommandAsync("... @p0, @p1", param1, param2 ..)
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();
    }
  }
}
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.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");
    }
Public class User
{
        public int Id { get; set; }
        public string fname { get; set; }
        public string lname { get; set; }
        public string username { get; set; }
}
var userList = datacontext.Database.SqlQuery<User>(@"SELECT u.Id ,fname , lname ,username FROM dbo.Users").ToList<User>();
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);
public class PeopleView
{
    public int ID { get; set; }
    public string Name { get; set; }
}
Public class School
{
    [Key]
    public Guid SchoolId { get; set; }
    public string Name { get; set; }
    public string Branch { get; set; }
    public int NumberOfStudents  { get; set; }
}
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();
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;
                }
            }
                
        }
    }

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

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