[c#] DbSet없는 원시 SQL 쿼리-Entity Framework Core

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

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

어떤 아이디어 ???



답변

EF Core 2.1 또는 EF Core 3 이상 버전을 사용하는지 여부에 따라 다릅니다 .

EF Core 2.1을 사용하는 경우

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

또한주의 DdContext의이 부분 클래스 당신이 가장 적합한대로 ‘원시 SQL DbQuery’정의를 구성하는 하나 개 이상의 별도의 파일을 생성 할 수 있습니다.


EF Core 3.0 이상 버전을 사용하는 경우

쿼리 유형은 이제 키 가없는 엔티티 유형으로 알려져 있습니다. 위에서 언급했듯이 쿼리 유형은 EF Core 2.1에서 도입되었습니다. EF Core 3.0 이상 버전을 사용하는 경우 이제 쿼리 유형이 사용되지 않음으로 표시되므로 키가없는 tntity 유형 사용을 고려해야합니다.

이 기능은 쿼리 유형의 이름으로 EF Core 2.1에 추가되었습니다. EF Core 3.0에서는 개념이 키가없는 엔터티 유형으로 이름이 변경되었습니다. [Keyless] 데이터 주석은 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();


답변

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

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해당 클래스에 대해 정의해야합니다 . 귀하의 경우 순위 를 정의해야합니다 .

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

자세한 정보는 여기 에서 찾을 수 있습니다 .


답변

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

그리고 사용법 :

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