To rozwiązanie opiera się w dużej mierze na rozwiązaniu firmy @pius. Chciałem dodać opcję obsługi parametrów zapytania, aby pomóc złagodzić wstrzykiwanie SQL, a także chciałem, aby było to rozszerzenie poza DbContext DatabaseFacade dla Entity Framework Core, aby było trochę bardziej zintegrowane.
Najpierw utwórz nową klasę z rozszerzeniem:
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
{
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;
}
}
}
}
}
Zauważ w powyższym, że „T” jest typem zwracanej wartości, a „P” jest typem parametrów zapytania, które będą się różnić w zależności od tego, czy używasz MySql, Sql, itd.
Następnie pokażemy przykład. Używam możliwości MySql EF Core, więc zobaczymy, jak możemy użyć powyższego rozszerzenia ogólnego z tą bardziej szczegółową implementacją MySql:
using EF.Extend;
namespace Car.Api.Controllers
{
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;
private readonly CarContext _context;
public CarController(ILogger<CarController> logger, CarContext context)
{
_logger = logger;
_context = context;
}
[HttpGet]
public IEnumerable<Car> Get()
{
MySqlParameter p1 = new MySqlParameter
{
ParameterName = "id1",
Value = "25"
};
MySqlParameter p2 = new MySqlParameter
{
ParameterName = "id2",
Value = "26"
};
MySqlParameter p3 = new MySqlParameter
{
ParameterName = "id3",
Value = "27"
};
List<MySqlParameter> queryParameters = new List<MySqlParameter>() { p1, p2, p3 };
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;
}
}
}
Zapytanie zwróci takie wiersze, jak:
„Ford”, „Explorer”, „Ford Explorer”,
„Tesla”, „Model X”, „Tesla Model X”
Tytuł wyświetlany nie jest zdefiniowany jako kolumna bazy danych, więc domyślnie nie byłby częścią modelu EF Car. Podoba mi się to podejście jako jedno z wielu możliwych rozwiązań. Inne odpowiedzi na tej stronie odnoszą się do innych sposobów rozwiązania tego problemu za pomocą dekoratora [NotMapped], który w zależności od przypadku użycia może być bardziej odpowiednim podejściem.
Zwróć uwagę, że kod w tym przykładzie jest oczywiście bardziej szczegółowy, niż powinien, ale pomyślałem, że dzięki temu przykład jest jaśniejszy.