원시 SQL 쿼리Raw SQL Queries

Entity Framework Core를 사용하면 관계형 데이터베이스로 작업할 때 원시 SQL 쿼리로 드롭다운할 수 있습니다.Entity Framework Core allows you to drop down to raw SQL queries when working with a relational database. 원시 SQL 쿼리는 LINQ를 사용하여 원하는 쿼리를 표현할 수 없는 경우에 유용합니다.Raw SQL queries are useful if the query you want can't be expressed using LINQ. 원시 SQL 쿼리는 LINQ 쿼리를 사용하면 비효율적인 SQL 쿼리가 발생하는 경우에도 유용합니다.Raw SQL queries are also used if using a LINQ query is resulting in an inefficient SQL query. 원시 SQL 쿼리는 기본 엔터티 형식 또는 모델의 일부인 키 없는 엔터티 형식을 반환할 수 있습니다.Raw SQL queries can return regular entity types or keyless entity types that are part of your model.

GitHub에서 이 문서의 샘플을 볼 수 있습니다.You can view this article's sample on GitHub.

기본 원시 SQL 쿼리Basic raw SQL queries

FromSqlRaw 확장 메서드를 사용하여 원시 SQL 쿼리를 기반으로 한 LINQ 쿼리를 시작할 수 있습니다.You can use the FromSqlRaw extension method to begin a LINQ query based on a raw SQL query. FromSqlRaw는 쿼리 루트에만 사용할 수 있습니다. 즉, DbSet<>에 직접 사용할 수 있습니다.FromSqlRaw can only be used on query roots, that is directly on the DbSet<>.

var blogs = context.Blogs
    .FromSqlRaw("SELECT * FROM dbo.Blogs")
    .ToList();

원시 SQL 쿼리를 사용하여 저장 프로시저를 실행할 수 있습니다.Raw SQL queries can be used to execute a stored procedure.

var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogs")
    .ToList();

매개 변수 전달Passing parameters

경고

원시 SQL 쿼리에 항상 매개 변수화를 사용하세요.Always use parameterization for raw SQL queries

사용자가 제공한 값을 원시 SQL 쿼리에 도입할 때는 SQL 삽입 공격을 방지하기 위해 주의를 기울여야 합니다.When introducing any user-provided values into a raw SQL query, care must be taken to avoid SQL injection attacks. 이러한 값이 잘못된 문자를 포함하지 않은 것을 확인하는 것 외에도 항상 매개 변수화를 사용하여 SQL 텍스트와 별도로 값을 보내야 합니다.In addition to validating that such values don't contain invalid characters, always use parameterization which sends the values separate from the SQL text.

특히 유효성이 검사되지 않은 사용자 제공 값을 사용하여 연결되거나 보간된 문자열($"")을 FromSqlRaw 또는 ExecuteSqlRaw로 전달하면 안 됩니다.In particular, never pass a concatenated or interpolated string ($"") with non-validated user-provided values into FromSqlRaw or ExecuteSqlRaw. FromSqlInterpolatedExecuteSqlInterpolated 메서드를 사용하면 SQL 삽입 공격으로부터 보호하는 방식으로 문자열 보간 구문을 사용할 수 있습니다.The FromSqlInterpolated and ExecuteSqlInterpolated methods allow using string interpolation syntax in a way that protects against SQL injection attacks.

다음 예에서는 SQL 쿼리 문자열에 매개 변수 자리 표시자를 포함하고 추가 인수를 제공하여 단일 매개 변수를 저장 프로시저에 전달합니다.The following example passes a single parameter to a stored procedure by including a parameter placeholder in the SQL query string and providing an additional argument. 이 구문은 String.Format 구문처럼 보일 수 있지만 제공된 값은 DbParameter로 래핑되고 생성된 매개 변수 이름은 {0} 자리 표시자가 지정된 위치에 삽입됩니다.While this syntax may look like String.Format syntax, the supplied value is wrapped in a DbParameter and the generated parameter name inserted where the {0} placeholder was specified.

var user = "johndoe";

var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogsForUser {0}", user)
    .ToList();

FromSqlInterpolatedFromSqlRaw와 비슷하지만, 이를 사용하여 문자열 보간 구문을 사용할 수 있습니다.FromSqlInterpolated is similar to FromSqlRaw but allows you to use string interpolation syntax. FromSqlRaw와 마찬가지로 FromSqlInterpolated는 쿼리 루트에만 사용할 수 있습니다.Just like FromSqlRaw, FromSqlInterpolated can only be used on query roots. 이전 예제와 마찬가지로 값은 DbParameter로 변환되므로 SQL 삽입에 취약하지 않습니다.As with the previous example, the value is converted to a DbParameter and isn't vulnerable to SQL injection.

참고

버전 3.0 이전에는 FromSqlRawFromSqlInterpolatedFromSql이라는 두 오버로드였습니다.Prior to version 3.0, FromSqlRaw and FromSqlInterpolated were two overloads named FromSql. 자세한 내용은 이전 버전 섹션을 참조하세요.For more information, see the previous versions section.

var user = "johndoe";

var blogs = context.Blogs
    .FromSqlInterpolated($"EXECUTE dbo.GetMostPopularBlogsForUser {user}")
    .ToList();

DbParameter를 생성하고 매개 변수 값으로 제공할 수도 있습니다.You can also construct a DbParameter and supply it as a parameter value. 문자열 자리 표시자가 아닌 일반 SQL 매개 변수 자리 표시자가 사용되므로 FromSqlRaw를 안전하게 사용할 수 있습니다.Since a regular SQL parameter placeholder is used, rather than a string placeholder, FromSqlRaw can be safely used:

var user = new SqlParameter("user", "johndoe");

var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogsForUser @user", user)
    .ToList();

FromSqlRaw를 사용하면 SQL 쿼리 문자열에 명명된 매개 변수를 사용할 수 있으며, 이는 저장 프로시저가 선택적 매개 변수를 포함하는 경우 유용합니다.FromSqlRaw allows you to use named parameters in the SQL query string, which is useful when a stored procedure has optional parameters:

var user = new SqlParameter("user", "johndoe");

var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogsForUser @filterByUser=@user", user)
    .ToList();

참고

매개 변수 순서 지정 Entity Framework Core는 SqlParameter[] 배열의 순서에 따라 매개 변수를 전달합니다.Parameter Ordering Entity Framework Core passes parameters based on the order of the SqlParameter[] array. 여러 SqlParameter를 전달하는 경우 SQL 문자열의 순서 지정은 저장 프로시저 정의에 있는 매개 변수의 순서와 일치해야 합니다.When passing multiple SqlParameters, the ordering in the SQL string must match the order of the parameters in the stored procedure's definition. 이렇게 되지 않는 경우 프로시저가 실행될 때 형식 변환 예외 및/또는 예기치 않은 동작이 발생할 수 있습니다.Failure to do this may result in type conversion exceptions and/or unexpected behavior when the procedure is executed.

LINQ로 작성Composing with LINQ

LINQ 연산자를 사용하여 초기 원시 SQL 쿼리의 맨 위에 구성할 수 있습니다.You can compose on top of the initial raw SQL query using LINQ operators. EF Core는 이를 하위 쿼리로 취급하고 데이터베이스에서 이에 대해 구성합니다.EF Core will treat it as subquery and compose over it in the database. 다음 예제에서는 TVF(테이블 반환 함수)에서 선택하는 원시 SQL 쿼리를 사용한 후The following example uses a raw SQL query that selects from a Table-Valued Function (TVF). LINQ를 사용하여 이에 대해 구성하여 필터링과 정렬을 수행합니다.And then composes on it using LINQ to do filtering and sorting.

var searchTerm = ".NET";

var blogs = context.Blogs
    .FromSqlInterpolated($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
    .Where(b => b.Rating > 3)
    .OrderByDescending(b => b.Rating)
    .ToList();

위 쿼리는 다음 SQL을 생성합니다.Above query generates following SQL:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url]
FROM (
    SELECT * FROM dbo.SearchBlogs(@p0)
) AS [b]
WHERE [b].[Rating] > 3
ORDER BY [b].[Rating] DESC

Include 메서드는 다른 LINQ 쿼리와 마찬가지로 관련 데이터를 포함하는 데 사용할 수 있습니다.The Include method can be used to include related data, just like with any other LINQ query:

var searchTerm = ".NET";

var blogs = context.Blogs
    .FromSqlInterpolated($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
    .Include(b => b.Posts)
    .ToList();

LINQ를 사용하여 구성하려면 원시 SQL 쿼리가 구성 가능해야 합니다. EF Core가 제공된 SQL을 하위 쿼리로 취급하기 때문입니다.Composing with LINQ requires your raw SQL query to be composable since EF Core will treat the supplied SQL as a subquery. SQL 쿼리는 SELECT 키워드로 시작하여 작성할 수 있습니다.SQL queries that can be composed on begin with the SELECT keyword. 또한 전달된 SQL에는 다음과 같이 하위 쿼리에서 유효하지 않은 문자나 옵션을 포함할 수 없습니다.Further, SQL passed shouldn't contain any characters or options that aren't valid on a subquery, such as:

  • 후행 세미콜론A trailing semicolon
  • SQL Server의 후행 쿼리 수준 힌트(예: OPTION (HASH JOIN))On SQL Server, a trailing query-level hint (for example, OPTION (HASH JOIN))
  • SQL Server에서, SELECT 절에서 OFFSET 0 또는 TOP 100 PERCENT와 함께 사용되지 않는 ORDER BYOn SQL Server, an ORDER BY clause that isn't used with OFFSET 0 OR TOP 100 PERCENT in the SELECT clause

SQL Server는 저장 프로시저 호출에 대한 구성을 허용하지 않으므로 그러한 호출에 추가 쿼리 연산자를 적용하려고 하면 잘못된 SQL이 발생합니다.SQL Server doesn't allow composing over stored procedure calls, so any attempt to apply additional query operators to such a call will result in invalid SQL. EF Core가 저장 프로시저에 대해 구성을 시도하지 않도록 FromSqlRaw 또는 FromSqlInterpolated 메서드 직후에 AsEnumerable 또는 AsAsyncEnumerable 메서드를 사용하세요.Use AsEnumerable or AsAsyncEnumerable method right after FromSqlRaw or FromSqlInterpolated methods to make sure that EF Core doesn't try to compose over a stored procedure.

변경 내용 추적Change Tracking

FromSqlRaw 또는 FromSqlInterpolated 메서드를 사용하는 쿼리는 EF Core에서 다른 LINQ 쿼리와 완전히 동일한 변경 내용 추적 규칙을 따릅니다.Queries that use the FromSqlRaw or FromSqlInterpolated methods follow the exact same change tracking rules as any other LINQ query in EF Core. 예를 들어 쿼리 프로젝트 엔터티 형식의 경우 기본적으로 결과가 추적됩니다.For example, if the query projects entity types, the results will be tracked by default.

다음 예제에서는 TVF(테이블 반환 함수)에서 선택하는 원시 SQL 쿼리를 사용한 이후에 AsNoTracking을 호출하여 변경 내용 추적을 사용하지 않도록 설정합니다.The following example uses a raw SQL query that selects from a Table-Valued Function (TVF), then disables change tracking with the call to AsNoTracking:

var searchTerm = ".NET";

var blogs = context.Blogs
    .FromSqlInterpolated($"SELECT * FROM dbo.SearchBlogs({searchTerm})")
    .AsNoTracking()
    .ToList();

제한 사항Limitations

원시 SQL 쿼리를 사용할 때는 몇 가지 제한 사항에 유의해야 합니다.There are a few limitations to be aware of when using raw SQL queries:

  • SQL 쿼리는 엔터티 형식의 모든 속성에 대해 데이터를 반환해야 합니다.The SQL query must return data for all properties of the entity type.
  • 결과 집합의 열 이름은 속성이 매핑되는 열 이름과 일치해야 합니다.The column names in the result set must match the column names that properties are mapped to. 이 동작은 EF6와 다릅니다.Note this behavior is different from EF6. EF6은 속성/열 매핑을 원시 SQL 쿼리에 대해 무시했고 결과 집합 열 이름이 속성 이름과 일치해야 했습니다.EF6 ignored property to column mapping for raw SQL queries and result set column names had to match the property names.
  • SQL 쿼리는 관련 데이터를 포함할 수 없습니다.The SQL query can't contain related data. 그러나 대부분의 경우 쿼리의 맨 위에서 Include 연산자를 사용하여 관련 데이터를 반환하도록 작성할 수 있습니다(관련 데이터 포함 참조).However, in many cases you can compose on top of the query using the Include operator to return related data (see Including related data).

이전 버전Previous versions

EF Core 버전 2.2 및 이전 버전에는 최신 FromSqlRawFromSqlInterpolated와 동일한 방식으로 작동하는 FromSql 메서드라는 두 개의 오버로드가 있습니다.EF Core version 2.2 and earlier had two overloads of method named FromSql, which behaved in the same way as the newer FromSqlRaw and FromSqlInterpolated. 이로 인해 보간된 문자열 메서드를 호출하려다 실수로 원시 문자열 메서드를 호출하거나, 반대로 후자를 호출하려다 전자를 호출하는 실수를 저지를 가능성이 매우 높습니다.It was easy to accidentally call the raw string method when the intent was to call the interpolated string method, and the other way around. 실수로 잘못된 오버로드를 호출하면 매개 변수가 있어야 하는 쿼리에 매개 변수가 있지 않게 될 수 있습니다.Calling wrong overload accidentally could result in queries not being parameterized when they should have been.