使用Entity Framework Core删除dbData.Database.SqlQuery<SomeModel>
我无法找到为我的全文搜索查询构建原始SQL查询的解决方案,该查询将返回表数据以及排名。
我在实体框架核心中构建原始SQL查询的唯一方法是通过dbData.Product.FromSql("SQL SCRIPT");
这不是很有用,因为我没有DbSet可以映射我在查询。
任何想法???
答案 0 :(得分:59)
如果您使用自2018年7月7日起提供的EF Core 2.1 Release Candidate 1,您可以利用建议的新功能,即查询类型。
什么是query type?
除了实体类型,EF Core模型还可以包含查询类型, 可用于对数据进行数据库查询 未映射到实体类型。
何时使用查询类型?
用作ad hoc FromSql()查询的返回类型。
映射到数据库视图。
映射到未定义主键的表。
映射到模型中定义的查询。
所以你不再需要做所有的黑客或解决方案作为你的问题的答案。只需按照以下步骤操作:
首先,您定义了类型为DbQuery<T>
的新属性,其中T
是将携带SQL查询的列值的类的类型。所以在你的DbContext
中你会得到这个:
public DbQuery<SomeModel> SomeModels { get; set; }
其次使用FromSql
方法,就像使用DbSet<T>
:
var result = context.SomeModels.FromSql("SQL_SCRIPT").ToList();
var result = await context.SomeModels.FromSql("SQL_SCRIPT").ToListAsync();
另请注意,DBContexts为partial classes,因此您可以创建一个或多个单独的文件来组织最适合您的“原始SQL DbQuery”定义。
答案 1 :(得分:23)
在EF Core中,您不再可以执行“免费”原始sql。您需要为该类定义POCO类和DbSet
。
在您的情况下,您需要定义 Rank :
var ranks = DbContext.Ranks
.FromSql("SQL_SCRIPT OR STORED_PROCEDURE @p0,@p1,...etc", parameters)
.AsNoTracking().ToList();
因为它肯定是只读的,所以包含.AsNoTracking()
电话会很有用。
答案 2 :(得分:16)
您可以在EF Core中执行原始sql - 将此类添加到您的项目中。 这将允许您执行原始SQL并获得原始结果,而无需定义POCO和DBSet。 有关原始示例,请参阅https://github.com/aspnet/EntityFramework/issues/1862#issuecomment-220787464。
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.EntityFrameworkCore
{
public static class RDFacadeExtensions
{
public static RelationalDataReader ExecuteSqlQuery(this DatabaseFacade databaseFacade, string sql, params object[] parameters)
{
var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var rawSqlCommand = databaseFacade
.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return rawSqlCommand
.RelationalCommand
.ExecuteReader(
databaseFacade.GetService<IRelationalConnection>(),
parameterValues: rawSqlCommand.ParameterValues);
}
}
public static async Task<RelationalDataReader> ExecuteSqlQueryAsync(this DatabaseFacade databaseFacade,
string sql,
CancellationToken cancellationToken = default(CancellationToken),
params object[] parameters)
{
var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var rawSqlCommand = databaseFacade
.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return await rawSqlCommand
.RelationalCommand
.ExecuteReaderAsync(
databaseFacade.GetService<IRelationalConnection>(),
parameterValues: rawSqlCommand.ParameterValues,
cancellationToken: cancellationToken);
}
}
}
}
以下是如何使用它的示例:
// Execute a query.
using(var dr = await db.Database.ExecuteSqlQueryAsync("SELECT ID, Credits, LoginDate FROM SamplePlayer WHERE " +
"Name IN ('Electro', 'Nitro')"))
{
// Output rows.
var reader = dr.DbDataReader;
while (reader.Read())
{
Console.Write("{0}\t{1}\t{2} \n", reader[0], reader[1], reader[2]);
}
}
答案 3 :(得分:16)
在其他答案的基础上,我编写了这个帮助程序来完成任务,包括示例用法:
public static class Helper
{
public static List<T> RawSqlQuery<T>(string query, Func<DbDataReader, T> map)
{
using (var context = new DbContext())
{
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
var entities = new List<T>();
while (result.Read())
{
entities.Add(map(result));
}
return entities;
}
}
}
}
用法:
public class TopUser
{
public string Name { get; set; }
public int Count { get; set; }
}
var result = Helper.RawSqlQuery(
"SELECT TOP 10 Name, COUNT(*) FROM Users U"
+ " INNER JOIN Signups S ON U.UserId = S.UserId"
+ " GROUP BY U.Name ORDER BY COUNT(*) DESC",
x => new TopUser { Name = (string)x[0], Count = (int)x[1] });
result.ForEach(x => Console.WriteLine($"{x.Name,-25}{x.Count}"));
我计划在添加内置支持后立即将其删除。根据来自EF Core团队的Arthur Vickers的statement,这是2.0后的优先考虑事项。正在跟踪问题here。
答案 4 :(得分:5)
目前,在EFCore有新内容之前我会使用一个命令 并手动映射
using (var command = this.DbContext.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "SELECT ... WHERE ...> @p1)";
command.CommandType = CommandType.Text;
var parameter = new SqlParameter("@p1",...);
command.Parameters.Add(parameter);
this.DbContext.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
while (result.Read())
{
.... // Map to your entity
}
}
}
尝试使用SqlParameter来避免Sql注入。
dbData.Product.FromSql("SQL SCRIPT");
FromSql不能使用完整查询。示例如果要包含将被忽略的WHERE子句。
一些链接:
答案 5 :(得分:4)
在Core 2.1中,您可以执行以下操作:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<Ranks>();
}
然后定义SQL过程,如:
public async Task<List<Ranks>> GetRanks(string value1, Nullable<decimal> value2)
{
SqlParameter value1Input = new SqlParameter("@Param1", value1?? (object)DBNull.Value);
SqlParameter value2Input = new SqlParameter("@Param2", value2?? (object)DBNull.Value);
List<Ranks> getRanks = await this.Query<Ranks>().FromSql("STORED_PROCEDURE @Param1, @Param2", value1Input, value2Input).ToListAsync();
return getRanks;
}
这样就不会在你的数据库中创建Ranks模型。
现在,在您的控制器/操作中,您可以致电:
List<Ranks> gettingRanks = _DbContext.GetRanks(value1,value2).Result.ToListAsync();
这样您就可以调用Raw SQL Procedures。
答案 6 :(得分:3)
您可以使用此命令(来自https://github.com/aspnet/EntityFrameworkCore/issues/1862#issuecomment-451671168):
public static class SqlQueryExtensions
{
public static IList<T> SqlQuery<T>(this DbContext db, string sql, params object[] parameters) where T : class
{
using (var db2 = new ContextForQueryType<T>(db.Database.GetDbConnection()))
{
return db2.Query<T>().FromSql(sql, parameters).ToList();
}
}
private class ContextForQueryType<T> : DbContext where T : class
{
private readonly DbConnection connection;
public ContextForQueryType(DbConnection connection)
{
this.connection = connection;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// switch on the connection type name to enable support multiple providers
// var name = con.GetType().Name;
optionsBuilder.UseSqlServer(connection, options => options.EnableRetryOnFailure());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<T>();
base.OnModelCreating(modelBuilder);
}
}
}
答案 7 :(得分:1)
实际上,您可以创建一个通用存储库并执行类似的操作
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : BaseEntity
{
private readonly DataContext context;
private readonly DbSet<TEntity> dbSet;
public GenericRepository(DataContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public IEnumerable<TEntity> ExecuteCommandQuery(string command)
=> dbSet.FromSqlRaw(command);
}
答案 8 :(得分:1)
如果您只想运行查询并希望返回int
。 EFCore 2具有DbContext.Database.ExecuteSqlCommand("Yourqueryhere")
方法。
编辑:
ExecuteSqlCommand
和ExecuteSqlCommandAsync
在Microsoft.EntityFrameworkCore.Relational
命名空间中定义。确保引用了它。
答案 9 :(得分:1)
我的案例使用存储过程而不是原始SQL
创建了一个课程
Public class School
{
[Key]
public Guid SchoolId { get; set; }
public string Name { get; set; }
public string Branch { get; set; }
public int NumberOfStudents { get; set; }
}
在我的DbContext
课堂上添加了以下内容
public DbSet<School> SP_Schools { get; set; }
要执行存储过程:
var MySchools = _db.SP_Schools.FromSqlRaw("GetSchools @schoolId, @page, @size ",
new SqlParameter("schoolId", schoolId),
new SqlParameter("page", page),
new SqlParameter("size", size)))
.IgnoreQueryFilters();
答案 10 :(得分:0)
我从@AminRostami 更新了扩展方法以返回 IAsyncEnumerable(因此可以应用 LINQ 过滤)并且它将从数据库返回的记录的模型列名称映射到模型(使用 EF Core 5 测试):
扩展本身:
public static class QueryHelper
{
private static string GetColumnName(this MemberInfo info)
{
List<ColumnAttribute> list = info.GetCustomAttributes<ColumnAttribute>().ToList();
return list.Count > 0 ? list.Single().Name : info.Name;
}
/// <summary>
/// Executes raw query with parameters and maps returned values to column property names of Model provided.
/// Not all properties are required to be present in model (if not present - null)
/// </summary>
public static async IAsyncEnumerable<T> ExecuteQuery<T>(
[NotNull] this DbContext db,
[NotNull] string query,
[NotNull] params SqlParameter[] parameters)
where T : class, new()
{
await using DbCommand command = db.Database.GetDbConnection().CreateCommand();
command.CommandText = query;
command.CommandType = CommandType.Text;
if (parameters != null)
{
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
}
await db.Database.OpenConnectionAsync();
await using DbDataReader reader = await command.ExecuteReaderAsync();
List<PropertyInfo> lstColumns = new T().GetType()
.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
while (await reader.ReadAsync())
{
T newObject = new();
for (int i = 0; i < reader.FieldCount; i++)
{
string name = reader.GetName(i);
PropertyInfo prop = lstColumns.FirstOrDefault(a => a.GetColumnName().Equals(name));
if (prop == null)
{
continue;
}
object val = await reader.IsDBNullAsync(i) ? null : reader[i];
prop.SetValue(newObject, val, null);
}
yield return newObject;
}
}
}
使用的模型(注意列名与实际的属性名不同):
public class School
{
[Key] [Column("SCHOOL_ID")] public int SchoolId { get; set; }
[Column("CLOSE_DATE", TypeName = "datetime")]
public DateTime? CloseDate { get; set; }
[Column("SCHOOL_ACTIVE")] public bool? SchoolActive { get; set; }
}
实际使用情况:
public async Task<School> ActivateSchool(int schoolId)
{
// note that we're intentionally not returning "SCHOOL_ACTIVE" with select statement
// this might be because of certain IF condition where we return some other data
return await _context.ExecuteQuery<School>(
"UPDATE SCHOOL SET SCHOOL_ACTIVE = 1 WHERE SCHOOL_ID = @SchoolId; SELECT SCHOOL_ID, CLOSE_DATE FROM SCHOOL",
new SqlParameter("@SchoolId", schoolId)
).SingleAsync();
}
答案 11 :(得分:0)
此解决方案在很大程度上取决于@pius的解决方案。我想添加支持查询参数的选项,以帮助减轻SQL注入,我还想使其成为DbContext DatabaseFacade for Entity Framework Core的扩展,以使其更加集成。
首先用扩展名创建一个新类:
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;
}
}
}
查询将返回以下行:
“福特”,“资源管理器”,“福特资源管理器”
“ Tesla”,“ Model X”,“ Tesla Model X”
显示标题未定义为数据库列,因此默认情况下它不属于EF Car模型。我喜欢这种方法作为许多可能的解决方案之一。本页上的其他答案引用了其他方法来使用[NotMapped]装饰器解决此问题,根据您的使用情况,这可能是更合适的方法。
请注意,此示例中的代码显然比所需的更加冗长,但我认为它使示例更清晰。
答案 12 :(得分:0)
我知道这是一个老问题,但是也许它可以帮助某人调用存储过程而无需将DTO添加为DbSet。
答案 13 :(得分:0)
尝试一下:(创建扩展方法)
public static List<T> ExecuteQuery<T>(this dbContext db, string query) where T : class, new()
{
using (var command = db.Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
db.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
var lst = new List<T>();
var lstColumns = new T().GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
while (reader.Read())
{
var newObject = new T();
for (var i = 0; i < reader.FieldCount; i++)
{
var name = reader.GetName(i);
PropertyInfo prop = lstColumns.FirstOrDefault(a => a.Name.ToLower().Equals(name.ToLower()));
if (prop == null)
{
continue;
}
var val = reader.IsDBNull(i) ? null : reader[i];
prop.SetValue(newObject, val, null);
}
lst.Add(newObject);
}
return lst;
}
}
}
用法:
var db = new dbContext();
string query = @"select ID , Name from People where ... ";
var lst = db.ExecuteQuery<PeopleView>(query);
我的模型:(不在DbSet
中):
public class PeopleView
{
public int ID { get; set; }
public string Name { get; set; }
}
在
.netCore 2.2 and 3.0
中进行了测试。
注意:该解决方案的性能较慢
答案 14 :(得分:0)
使用Entity Framework 6,您可以执行以下操作
创建模态类为
Public class User
{
public int Id { get; set; }
public string fname { get; set; }
public string lname { get; set; }
public string username { get; set; }
}
执行原始DQL SQl命令,如下所示:
var userList = datacontext.Database.SqlQuery<User>(@"SELECT u.Id ,fname , lname ,username FROM dbo.Users").ToList<User>();
答案 15 :(得分:0)
不直接针对OP的场景,但是由于我一直在为此苦苦挣扎,所以我想删除这些ex。使使用DbContext
执行原始SQL更加容易的方法:
public static class DbContextCommandExtensions
{
public static async Task<int> ExecuteNonQueryAsync(this DbContext context, string rawSql,
params object[] parameters)
{
var conn = context.Database.GetDbConnection();
using (var command = conn.CreateCommand())
{
command.CommandText = rawSql;
if (parameters != null)
foreach (var p in parameters)
command.Parameters.Add(p);
await conn.OpenAsync();
return await command.ExecuteNonQueryAsync();
}
}
public static async Task<T> ExecuteScalarAsync<T>(this DbContext context, string rawSql,
params object[] parameters)
{
var conn = context.Database.GetDbConnection();
using (var command = conn.CreateCommand())
{
command.CommandText = rawSql;
if (parameters != null)
foreach (var p in parameters)
command.Parameters.Add(p);
await conn.OpenAsync();
return (T)await command.ExecuteScalarAsync();
}
}
}
答案 16 :(得分:0)
您也可以使用QueryFirst。像Dapper一样,这完全在EF之外。与Dapper(或EF)不同,您不需要维护POCO,您可以在真实的环境中编辑sql SQL,并且可以根据数据库不断对其进行重新验证。免责声明:我是QueryFirst的作者。
答案 17 :(得分:0)
我使用Dapper绕过了实体框架核心的约束。
IDbConnection.Query
正在使用带有多个参数的sql查询或存储过程。 顺便说一下,它要快一些(请参阅benchmark tests)
Dapper很容易学习。使用参数编写和运行存储过程花了15分钟。无论如何,您都可以同时使用EF和Dapper。下面是一个示例:
public class PodborsByParametersService
{
string _connectionString = null;
public PodborsByParametersService(string connStr)
{
this._connectionString = connStr;
}
public IList<TyreSearchResult> GetTyres(TyresPodborView pb,bool isPartner,string partnerId ,int pointId)
{
string sqltext "spGetTyresPartnerToClient";
var p = new DynamicParameters();
p.Add("@PartnerID", partnerId);
p.Add("@PartnerPointID", pointId);
using (IDbConnection db = new SqlConnection(_connectionString))
{
return db.Query<TyreSearchResult>(sqltext, p,null,true,null,CommandType.StoredProcedure).ToList();
}
}
}