이 솔루션은 @pius의 솔루션에 크게 의존합니다. SQL 주입을 완화하는 데 도움이되는 쿼리 매개 변수를 지원하는 옵션을 추가하고 싶었고 Entity Framework Core 용 DbContext DatabaseFacade에서 확장하여 좀 더 통합되도록 만들고 싶었습니다.
먼저 확장을 사용하여 새 클래스를 만듭니다.
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;
}
}
}
쿼리는
"Ford", "Explorer", "Ford Explorer"
"Tesla", "Model X", "Tesla Model X" 와 같은 행을 반환합니다.
표시 제목은 데이터베이스 열로 정의되지 않으므로 기본적으로 EF Car 모델의 일부가 아닙니다. 저는이 접근 방식을 가능한 많은 솔루션 중 하나로 좋아합니다. 이 페이지의 다른 답변은 [NotMapped] 데코레이터로이 문제를 해결하는 다른 방법을 참조하며, 사용 사례에 따라 더 적절한 접근 방식이 될 수 있습니다.
이 예제의 코드는 필요한 것보다 분명히 더 장황하지만 예제가 더 명확 해 졌다고 생각했습니다.