Solusi ini sangat bergantung pada solusi dari @pius. Saya ingin menambahkan opsi untuk mendukung parameter kueri guna membantu mengurangi injeksi SQL dan saya juga ingin menjadikannya perpanjangan dari DbContext DatabaseFacade untuk Entity Framework Core untuk membuatnya sedikit lebih terintegrasi.
Pertama buat kelas baru dengan ekstensi:
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;
}
}
}
}
}
Perhatikan di atas bahwa "T" adalah tipe untuk pengembalian dan "P" adalah jenis parameter kueri Anda yang akan bervariasi berdasarkan jika Anda menggunakan MySql, Sql, dan seterusnya.
Selanjutnya kami akan menunjukkan contoh. Saya menggunakan kemampuan MySql EF Core, jadi kita akan melihat bagaimana kita dapat menggunakan ekstensi generik di atas dengan implementasi MySql yang lebih spesifik ini:
//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;
}
}
}
Kueri akan mengembalikan baris seperti:
"Ford", "Explorer", "Ford Explorer"
"Tesla", "Model X", "Tesla Model X"
Judul tampilan tidak didefinisikan sebagai kolom database, jadi tidak akan menjadi bagian dari model Mobil EF secara default. Saya menyukai pendekatan ini sebagai salah satu dari banyak kemungkinan solusi. Jawaban lain di halaman ini merujuk pada cara lain untuk mengatasi masalah ini dengan dekorator [NotMapped], yang bergantung pada kasus penggunaan Anda bisa menjadi pendekatan yang lebih tepat.
Perhatikan kode dalam contoh ini jelas lebih bertele-tele daripada yang seharusnya, tetapi saya pikir itu membuat contoh lebih jelas.