После удаления Entity Framework Core dbData.Database.SqlQuery<SomeModel> я не могу найти решение для создания необработанного SQL-запроса для моего полнотекстового поискового запроса, который вернет данные таблиц, а также рейтинг.

Единственный метод, который я видел для создания необработанного SQL-запроса в Entity Framework Core, - это dbData.Product.FromSql("SQL SCRIPT");, который бесполезен, поскольку у меня нет DbSet, который будет отображать ранг, который я возвращаю в запросе.

Есть идеи ???

121
David Harlow 25 Фев 2016 в 18:44

17 ответов

Лучший ответ

Это зависит от того, используете ли вы EF Core 2.1 или EF Core 3 и более поздние версии .

Если вы используете EF Core 2.1

Если вы используете EF Core 2.1 Release Candidate 1, доступный с 7 мая 2018 г., вы можете воспользоваться преимуществами предлагаемой новой функции - типа запроса.

Что такое тип запроса?

Помимо типов сущностей, модель EF Core может содержать типы запросов, которые можно использовать для выполнения запросов к базе данных с данными, которые не сопоставлены с типами сущностей.

Когда использовать тип запроса?

Служит в качестве возвращаемого типа для специальных запросов FromSql ().

Сопоставление с представлениями базы данных.

Сопоставление с таблицами, для которых не определен первичный ключ.

Сопоставление с запросами, определенными в модели.

Таким образом, вам больше не нужно использовать все хитрости или обходные пути, предложенные в качестве ответов на ваш вопрос. Просто выполните следующие действия:

Сначала вы определили новое свойство типа DbQuery<T>, где T - это тип класса, который будет нести значения столбцов вашего 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();

Также обратите внимание, что DdContext являются частичные классы, поэтому вы можете создать один или несколько отдельных файлов для организации ваших определений" сырого SQL DbQuery "в соответствии с вашими предпочтениями.


Если вы используете EF Core 3.0 и более поздние версии

Тип запроса теперь известен как Keyless тип объекта. Как сказано выше, типы запросов были введены в EF Core 2.1. Если вы используете EF Core 3.0 или более позднюю версию, вам следует теперь использовать типы tntity без ключа, поскольку типы запросов теперь помечены как устаревшие.

Эта функция была добавлена в 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();
139
CodeNotFound 27 Май 2020 в 09:37

На самом деле вы можете создать общий репозиторий и сделать что-то вроде этого

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

}
0
Denis 1 Сен 2020 в 11:05

В моем случае использовалась хранимая процедура вместо необработанного 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
marc_s 22 Мар 2020 в 11:46

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

Таким образом, вы можете вызывать процедуры Raw SQL.

7
RodrigoCampos 19 Июн 2018 в 10:22

На данный момент, пока не появится что-то новое от 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, оно будет проигнорировано.

Некоторые ссылки:

Выполнение сырых SQL-запросов с использованием Entity Framework Core

Необработанные SQL-запросы

19
Bohdan Stupak 23 Май 2019 в 11:41

Вы можете выполнить необработанный sql в EF Core - добавьте этот класс в свой проект. Это позволит вам выполнять необработанный 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]);
    }
}
21
Yehuda Goldenberg 21 Мар 2017 в 16:34

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

Дополнительную информацию можно найти на здесь

31
E-Bat 24 Апр 2020 в 02:35

Добавить пакет Nuget - Microsoft.EntityFrameworkCore.Relational

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

Это вернет номера строк как int

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

7
LuvForAirplanes 24 Фев 2020 в 20:24

Основываясь на других ответах, я написал этот помощник, который выполняет задачу, включая пример использования:

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, это высокий приоритет для поста 2.0. Проблема отслеживается здесь.

41
pius 19 Июн 2018 в 11:36

Вы можете использовать это (из 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");
    }
8
ErikEJ 24 Окт 2020 в 06:10

Попробуйте это: (создать метод расширения)

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.

Примечание . У этого решения низкая производительность.

6
AminRostami 10 Ноя 2019 в 11:04

Не нацелен непосредственно на сценарий 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();
    }
  }
}
2
Shimmy Weitzhandler 24 Дек 2018 в 22:07

Я использовал Dapper, чтобы обойти это ограничение Entity framework Core.

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


        }
}
1
Lapenkov Vladimir 4 Дек 2018 в 09:56

Вы также можете использовать QueryFirst. Как и Dapper, это полностью вне EF. В отличие от Dapper (или EF), вам не нужно поддерживать POCO, вы редактируете свой sql SQL в реальной среде, и он постоянно проверяется на соответствие базе данных. Отказ от ответственности: я являюсь автором QueryFirst.

0
bbsimonbb 5 Дек 2018 в 10:44

Я знаю, что это старый вопрос, но, возможно, он поможет кому-то вызывать хранимые процедуры без добавления DTO в качестве DbSets.

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

0
Andrei 28 Май 2020 в 07:02

С 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>();
-7
Siddhartha 10 Июл 2019 в 18:54

Это решение во многом опирается на решение @pius. Я хотел добавить возможность поддержки параметров запроса, чтобы помочь смягчить SQL-инъекцию, и я также хотел сделать его расширением DbContext DatabaseFacade для 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;
       }
    }
}

Запрос вернет такие строки, как:
«Форд», «Эксплорер», «Форд Эксплорер»
«Тесла», «Модель X», «Тесла Модель X»

Отображаемый заголовок не определен как столбец базы данных, поэтому по умолчанию он не будет частью модели EF Car. Мне этот подход нравится как одно из многих возможных решений. Другие ответы на этой странице ссылаются на другие способы решения этой проблемы с помощью декоратора [NotMapped], который в зависимости от вашего варианта использования может быть более подходящим подходом.

Обратите внимание, что код в этом примере, очевидно, более подробен, чем должен быть, но я думал, что он сделал пример более понятным.

0
dan-iel 9 Авг 2020 в 18:22