Diese Lösung stützt sich stark auf die Lösung von @pius. Ich wollte die Option zur Unterstützung von Abfrageparametern hinzufügen, um die SQL-Injection zu verringern, und ich wollte es auch zu einer Erweiterung des DbContext DatabaseFacade für Entity Framework Core machen, um es ein wenig integrierter zu machen.
Erstellen Sie zunächst eine neue Klasse mit der Erweiterung:
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;
}
}
}
}
}
Beachten Sie oben, dass "T" der Typ für die Rückgabe ist und "P" der Typ Ihrer Abfrageparameter ist, der abhängig davon variiert, ob Sie MySql, Sql usw. verwenden.
Als nächstes zeigen wir ein Beispiel. Ich verwende die MySql EF Core-Funktion, daher werden wir sehen, wie wir die oben genannte generische Erweiterung mit dieser spezifischeren MySql-Implementierung verwenden können:
//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;
}
}
}
Die Abfrage würde Zeilen wie
"Ford", "Explorer", "Ford Explorer",
"Tesla", "Modell X", "Tesla-Modell X" zurückgeben.
Der Anzeigetitel ist nicht als Datenbankspalte definiert, sodass er standardmäßig nicht Teil des EF Car-Modells ist. Ich mag diesen Ansatz als eine von vielen möglichen Lösungen. Die anderen Antworten auf dieser Seite verweisen auf andere Möglichkeiten, um dieses Problem mit dem Dekorator [NotMapped] zu beheben. Dies kann je nach Anwendungsfall der geeignetere Ansatz sein.
Beachten Sie, dass der Code in diesem Beispiel offensichtlich ausführlicher ist, als er sein muss, aber ich dachte, er macht das Beispiel klarer.