IT TIP

DbSet없는 원시 SQL 쿼리-Entity Framework Core

itqueen 2020. 10. 31. 10:20
반응형

DbSet없는 원시 SQL 쿼리-Entity Framework Core


Entity Framework Core를 제거 dbData.Database.SqlQuery<SomeModel>하면 테이블 데이터와 순위를 반환하는 전체 텍스트 검색 쿼리에 대한 원시 SQL 쿼리를 빌드하는 솔루션을 찾을 수 없습니다.

Entity Framework Core에서 원시 SQL 쿼리를 작성하는 유일한 방법은 쿼리에서 dbData.Product.FromSql("SQL SCRIPT");반환하는 순위를 매핑하는 DbSet이 없기 때문에 유용하지 않습니다.

어떤 아이디어 ???


2018 년 5 월 7 일 이후로 제공되는 EF Core 2.1 릴리스 후보 1을 사용하는 경우 쿼리 유형 인 제안 된 새 기능을 활용할 수 있습니다.

쿼리 유형 이란 무엇입니까 ?

엔터티 형식 외에도 EF Core 모델에는 엔터티 형식에 매핑되지 않은 데이터에 대해 데이터베이스 쿼리를 수행하는 데 사용할 수있는 쿼리 형식이 포함될 수 있습니다.

쿼리 유형은 언제 사용합니까?

임시 FromSql () 쿼리의 반환 유형 역할을합니다.

데이터베이스보기에 매핑.

기본 키가 정의되지 않은 테이블에 매핑.

모델에 정의 된 쿼리에 매핑.

따라서 더 이상 질문에 대한 답변으로 제안 된 모든 해킹이나 해결 방법을 수행 할 필요가 없습니다. 다음 단계를 따르십시오.

먼저 형식의 새 속성 정의 하여 SQL 쿼리의 열 값을 수행하는 클래스의 유형입니다. 그래서 당신은 이것을 가질 것입니다 :DbQuery<T>TDbContext

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

또한 DBContexts는 부분 클래스 이므로 하나 이상의 개별 파일을 만들어 '원시 SQL DbQuery'정의를 가장 적합하게 구성 할 수 있습니다.


EF Core에서는 더 이상 "무료"원시 SQL을 실행할 수 없습니다. POCO 클래스와 DbSet해당 클래스에 대해 정의해야합니다 . 귀하의 경우 순위 를 정의해야합니다 .

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

확실히 읽기 전용이므로 .AsNoTracking()호출 을 포함하는 것이 유용 할 것 입니다.


다른 답변을 기반으로 예제 사용을 포함하여 작업을 수행하는이 도우미를 작성했습니다.

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 이후의 최우선 순위입니다. 여기에서 문제를 추적하고 있습니다 .


EF Core에서 원시 SQL을 실행할 수 있습니다.이 클래스를 프로젝트에 추가합니다. 이렇게하면 POCO 및 DBSet을 정의하지 않고도 원시 SQL을 실행하고 원시 결과를 얻을 수 있습니다. 원본 예제는 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]);
    }
}

지금은 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
         }
      }
  }

Sql 주입을 피하기 위해 SqlParameter를 시도하십시오.

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

FromSql은 전체 쿼리에서 작동하지 않습니다. 예를 들어 WHERE 절을 포함하려는 경우 무시됩니다.

일부 링크 :

Entity Framework Core를 사용하여 원시 SQL 쿼리 실행

원시 SQL 쿼리


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

이렇게하면 순위 모델이 DB에 생성되지 않습니다.

이제 컨트롤러 / 액션에서 다음을 호출 할 수 있습니다.

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

이렇게하면 원시 SQL 프로 시저를 호출 할 수 있습니다.


이것을 사용할 수 있습니다 ( 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);
        }
    }
}

OP의 시나리오를 직접 목표로하는 것은 아니지만,이 문제로 어려움을 겪고 있었기 때문에이 전직을 삭제하고 싶습니다. 다음을 사용하여 원시 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();
    }
  }
}

쿼리를 실행하고 int돌아 가기를 원할 경우 . EFCore 2에는 DbContext.Database.ExecuteSqlCommand("Yourqueryhere")메서드가 있습니다.

편집하다:

ExecuteSqlCommand과은 ExecuteSqlCommandAsync에 정의 된 Microsoft.EntityFrameworkCore.Relational네임 스페이스. 참조되었는지 확인하십시오.


Entity Framework Core의 제약 조건을 우회하기 위해 Dapper사용했습니다 .

IDbConnection.Query

is working with either sql query or stored procedure with multiple parameters. By the way it's a bit faster (see benchmark tests )

Dapper is easy to learn. It took 15 minutes to write and run stored procedure with parameters. Anyway you may use both EF and Dapper. Below is an example:

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


        }
}

You can also use QueryFirst. Like Dapper, this is totally outside EF. Unlike Dapper (or EF), you don't need to maintain the POCO, you edit your sql SQL in a real environment, and it's continually revalidated against the DB. Disclaimer: I'm the author of QueryFirst.


With Entity Framework 6 you can execute something like below

Create Modal Class as

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

Execute Raw DQL SQl command as below:

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

참고URL : https://stackoverflow.com/questions/35631903/raw-sql-query-without-dbset-entity-framework-core

반응형