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}"));
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
}
}
}
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.Entity<T>().HasNoKey();
base.OnModelCreating(modelBuilder);
}
}
}
以及用法:
using (var db = new Db())
{
var results = db.SqlQuery<ArbitraryType>("select 1 id, 'joe' name");
//or with an anonymous type like this
var results2 = db.SqlQuery(() => new { id =1, name=""},"select 1 id, 'joe' name");
}
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();
}
}
}
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();
这个解决方案严重依赖于@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;
}
}
}
}
}
//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;
}
}
}
18条答案
按热度按时间oewdyzsn1#
这取决于您使用的是ef core 2.1还是ef core 3及更高版本。
如果您使用的是ef core 2.1
如果您使用的是自2018年5月7日起推出的ef core 2.1 release candidate 1,那么您可以利用提议的新功能,即查询类型。
什么是查询类型?
除了实体类型之外,ef核心模型还可以包含查询类型,这些类型可用于对未Map到实体类型的数据执行数据库查询。
何时使用查询类型?
用作临时fromsql()查询的返回类型。
Map到数据库视图。
Map到未定义主键的表。
Map到模型中定义的查询。
因此,你不再需要做所有的黑客或解决方案提出的答案你的问题。只需遵循以下步骤:
首先定义了类型为的新属性
DbQuery<T>
哪里T
将携带sql查询的列值的类的类型。所以在你的DbContext
您将拥有:二次使用
FromSql
像你这样的方法DbSet<T>
:还要注意的是
DdContext
s是分部类,因此您可以创建一个或多个单独的文件来组织最适合您的“原始sqldbquery”定义。如果您使用的是ef core 3.0及更高版本
查询类型现在称为无键实体类型。如上所述,查询类型是在efcore2.1中引入的。如果您使用的是efcore3.0或更高版本,那么现在应该考虑使用无键实体类型,因为查询类型现在被标记为过时。
这个特性是在efcore2.1中以查询类型的名称添加的。在efcore3.0中,这个概念被重命名为无键实体类型。[keyless]数据注解在efcore 5.0中可用。
对于何时使用无键实体类型,我们仍然有与查询类型相同的场景。
所以要使用它,你需要首先标记你的班级
SomeModel
与[Keyless]
数据注解或通过fluent配置.HasNoKey()
方法调用如下:配置之后,您可以使用这里介绍的方法之一执行sql查询。例如,您可以使用以下方法:
k2arahey2#
在其他答案的基础上,我编写了这个帮助程序来完成任务,包括示例用法:
用法:
我计划在添加了内置支持后立即将其删除。根据来自ef核心团队的arthur vickers的一份声明,这是2.0后的一个高度优先事项。这个问题正在这里追踪。
pexxcrt23#
在efcore中,您不能再执行“free”原始sql。您需要定义一个poco类和一个
DbSet
为了那个班。在您的情况下,您需要定义等级:因为它肯定是只读的,所以包含
.AsNoTracking()
打电话。编辑-ef core 3.0中的突破性变化:
dbquery()现在已经过时,应该(再次)使用dbset()。如果您有一个无键实体,即它不需要主键,则可以使用hasnokey()方法:
更多信息可以在这里找到
avwztpqn4#
目前,在efcore有新功能之前,我会使用命令并手动Map它
尝试使用sqlparameter来避免sql注入。
fromsql不适用于完整查询。例如,如果您想包含where子句,它将被忽略。
一些链接:
使用实体框架核心执行原始sql查询
原始sql查询
b5buobof5#
您可以在ef core中执行原始sql-将这个类添加到您的项目中。这将允许您执行原始sql并获得原始结果,而无需定义poco和dbset。看到了吗https://github.com/aspnet/entityframework/issues/1862#issuecomment-220787464作为原始示例。
下面是一个如何使用它的示例:
uyhoqukh6#
你可以用这个(从https://github.com/aspnet/entityframeworkcore/issues/1862#issuecomment-451671168 ) :
以及用法:
3ks5zfa07#
在core 2.1中,您可以执行以下操作:
然后定义sql过程,如:
这样就不会在数据库中创建等级模型。
现在在控制器/操作中,您可以调用:
这样就可以调用原始sql过程。
5cg8jx4n8#
试试这个:(创建扩展方法)
用法:
我的模特:(不在)
DbSet
):测试时间
.netCore 2.2 and 3.0
.注意:此解决方案性能较慢
9w11ddsr9#
添加nuget包-microsoft.entityframeworkcore.relational
这将以整数形式返回行号
见-https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.executesqlcommand?view=efcore-3.0
jjhzyzn010#
不是直接针对op的场景,但是因为我一直在努力解决这个问题,所以我想放弃这些ex.方法,这些方法使得使用
DbContext
:lsmepo6l11#
实际上,您可以创建一个通用存储库并执行以下操作
llmtgqce12#
我使用dapper绕过实体框架核心的约束。
正在使用sql查询或具有多个参数的存储过程。顺便说一下,它要快一点(参见基准测试)
潇洒易学。用参数编写和运行存储过程花了15分钟。不管怎样,你可以同时使用ef和dapper。下面是一个例子:
zlwx9yxi13#
我的案例使用存储过程而不是原始sql
创建了一个类
在我的
DbContext
班要执行存储过程:
mwkjh3gx14#
这个解决方案严重依赖于@pius的解决方案。我想添加支持查询参数的选项来帮助减轻sql注入,还想将其作为dbcontext databasefacade for entity framework core的扩展,使其更加集成。
首先用扩展名创建一个新类:
请注意,在上面,“t”是返回的类型,“p”是查询参数的类型,如果您使用的是mysql、sql等,则查询参数的类型会有所不同。
接下来我们将展示一个示例。我使用的是mysql ef核心功能,因此我们将看到如何将上面的通用扩展与更具体的mysql实现结合使用:
查询将返回如下行:
“福特”,“探险家”,“福特探险家”
“特斯拉”,“x型”,“特斯拉x型”
显示标题没有定义为数据库列,因此默认情况下它不会是ef car模型的一部分。我喜欢这种方法作为许多可能的解决方案之一。本页上的其他答案引用了[notmapped]decorator解决这个问题的其他方法,根据您的用例,这可能是更合适的方法。
请注意,这个示例中的代码显然比它需要的更详细,但我认为它使示例更清晰。
iugsix8n15#
也可以使用queryfirst。像潇洒,这是完全以外的环境足迹。与dapper(或ef)不同的是,您不需要维护poco,您可以在真实的环境中编辑您的sql,并且它会不断地根据db进行重新验证。免责声明:我是queryfirst的作者。