没有DbSet的原始SQL查询 - 实体框架核心

Dav*_*low 79 c# entity-framework-core

使用Entity Framework Core删除dbData.Database.SqlQuery<SomeModel>我无法找到为我的全文搜索查询构建原始SQL查询的解决方案,该查询将返回表数据以及排名.

我见过在Entity Framework Core中构建原始SQL查询的唯一方法是通过dbData.Product.FromSql("SQL SCRIPT");它没有用,因为我没有DbSet来映射我在查询中返回的排名.

有任何想法吗???

Cod*_*und 92

如果您使用自2018年7月7日起可用的EF Core 2.1 Release Candidate 1,则可以利用建议的新功能,即查询类型.

什么是查询类型

除了实体类型之外,EF Core模型还可以包含查询类型,这些查询类型可用于对未映射到实体类型的数据执行数据库查询.

何时使用查询类型?

用作ad hoc FromSql()查询的返回类型.

映射到数据库视图.

映射到未定义主键的表.

映射到模型中定义的查询.

所以你不再需要做所有的黑客或解决方案作为你的问题的答案.只需按以下步骤操作:

首先,您定义了一个类型的新属性,DbQuery<T>其中T,该类是将携带SQL查询的列值的类的类型.所以,DbContext你将拥有这个:

public DbQuery<SomeModel> SomeModels { get; set; }
Run Code Online (Sandbox Code Playgroud)

其次FromSql像你一样使用方法DbSet<T>:

var result = context.SomeModels.FromSql("SQL_SCRIPT").ToList();
var result = await context.SomeModels.FromSql("SQL_SCRIPT").ToListAsync();
Run Code Online (Sandbox Code Playgroud)

另请注意,DBContexts是部分类,因此您可以创建一个或多个单独的文件来组织最适合您的"原始SQL DbQuery"定义.

  • 使用EF Core 2.1及更高版本时,此答案应该是最佳解决方案. (16认同)
  • 仅供参考,由于 EF core 3.0 中的一些错误,代码优先迁移仍会尝试创建表,即使在标记有 HasNoKey() 的实体上也是如此。所以你还必须添加.ToView(null)。例如 `modelBuilder.Entity&lt;MyData&gt;().HasNoKey().ToView(null);` @Jean-Paul 我认为这可以解决您的问题 (12认同)
  • 使用 CodeFirst 会自动创建一个包含所有这些属性的表,将“[NotMapped]”添加到“SomeModels”类对我来说不起作用。我错过了什么吗? (9认同)
  • EF Core 3.0 弃用了“DbQuery”,而只使用“DbSet”和[无键实体类型](https://docs.microsoft.com/en-us/ef/core/modeling/keyless-entity-types)。 (8认同)
  • @CodeNotFound 如果我不需要结果或者它是原始类型(例如`bit`)怎么办? (3认同)
  • @AnthonyGriggs 版本 5 支持此... modelBuilder.Entity&lt;ApplicationUser&gt;().ToTable("ApplicationUsers", t =&gt; t.ExcludeFromMigrations()); (3认同)
  • stann1 关于 ToView 的评论应该添加到实际答案中,这将为很多人节省大量时间。 (2认同)
  • 那么要在没有 DbSet 的情况下运行 Sql 查询,您需要定义 DbSet 吗?知道了谢谢。 (2认同)
  • 我不太确定发生了什么,但在 EF Core 5.0.3 中,这不再起作用......即使使用 modelBuilder.Entity&lt;MyData&gt;().HasNoKey().ToView(null); 它仍然尝试首先使用代码创建表。一分钟我想到了 modelBuilder.Ignore&lt;MyData&gt;(); 我发现另一个建议可行,但不行。令人沮丧的是,我多么想念 Database.SqlQuery&lt;SomeModel&gt; 它很简单,最重要的是它工作正常!有没有人仍然遇到同样的问题找到解决方法? (2认同)

E-B*_*Bat 26

在EF Core中,您不再可以执行"免费"原始sql.您需要为该类定义POCO类和a DbSet.在您的情况下,您将需要定义Rank:

var ranks = DbContext.Ranks
   .FromSql("SQL_SCRIPT OR STORED_PROCEDURE @p0,@p1,...etc", parameters)
   .AsNoTracking().ToList();
Run Code Online (Sandbox Code Playgroud)

因为它肯定是只读的,所以包含这个.AsNoTracking()电话会很有用.

  • 所以我想我还必须扩展`DbContext`以包含一个新属性`DbSet <Rank> Rank {get; 组; }`.现在对linq有何影响?也就是说我们现在不能使用像`DBContext.Rank.Where(i => i.key == 1)`这样的语句,并且这个语句在SQL中没有实现,因此会失败吗? (3认同)
  • 我的 DbSet 没有“FromSql”方法。这是我缺少的扩展吗? (2认同)
  • @birwin,您需要导入命名空间 Microsoft.EntityFrameworkCore (2认同)

piu*_*ius 24

在其他答案的基础上,我编写了这个帮助程序来完成任务,包括示例用法:

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;
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

用法:

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}"));
Run Code Online (Sandbox Code Playgroud)

我计划在添加内置支持后立即将其删除.根据EF Core团队的Arthur Vickers的一份声明,它是2.0后的重中之重.这个问题正在跟踪这里.

  • 您还可以以安全的方式添加参数吗? (2认同)

Yeh*_*erg 19

您可以在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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是如何使用它的示例:

// 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]);
    }
}
Run Code Online (Sandbox Code Playgroud)


Eri*_*kEJ 11

您可以使用它(来自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.Entity<T>().HasNoKey();
            base.OnModelCreating(modelBuilder);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以及用法:

    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");
    }
Run Code Online (Sandbox Code Playgroud)


Hen*_*nry 7

现在,在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
         }
      }
  }
Run Code Online (Sandbox Code Playgroud)

尝试使用SqlParameter避免Sql注入。

 dbData.Product.FromSql("SQL SCRIPT");
Run Code Online (Sandbox Code Playgroud)

FromSql不适用于完整查询。例如,如果您想包含WHERE子句,它将被忽略。

一些链接:

使用Entity Framework Core执行原始SQL查询

原始SQL查询


A.R*_*R.F 7

试试这个:(创建扩展方法)

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;
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

用法:

var db = new dbContext();
string query = @"select ID , Name from People where ... ";
var lst = db.ExecuteQuery<PeopleView>(query);
Run Code Online (Sandbox Code Playgroud)

我的模型:(不在DbSet):

public class PeopleView
{
    public int ID { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

中测试.netCore 2.2 and 3.0

注意:此解决方案性能较慢


Rod*_*pos 6

在Core 2.1中,您可以执行以下操作:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
       modelBuilder.Query<Ranks>();
}
Run Code Online (Sandbox Code Playgroud)

然后定义您的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;
}
Run Code Online (Sandbox Code Playgroud)

这样,将不会在数据库中创建Ranks模型。

现在,在您的控制器/操作中,您可以调用:

List<Ranks> gettingRanks = _DbContext.GetRanks(value1,value2).Result.ToListAsync();
Run Code Online (Sandbox Code Playgroud)

这样,您可以调用Raw SQL Procedures。


Moh*_*sin 6

添加 Nuget 包 - Microsoft.EntityFrameworkCore.Relational

using Microsoft.EntityFrameworkCore;
...
await YourContext.Database.ExecuteSqlCommandAsync("... @p0, @p1", param1, param2 ..)
Run Code Online (Sandbox Code Playgroud)

这会将行号作为 int 返回

请参阅 - https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.executesqlcommand?view=efcore-3.0

  • 请注意,这只会返回受命令影响的行数:/sf/answers/3490325961/ (4认同)

小智 6

我在github上找到了EntityFrameworkCore.RawSQLExtensions包。要使用它,请添加 nuget 包。

<PackageReference Include="EntityFrameworkCore.RawSQLExtensions" Version="1.2.0" />
Run Code Online (Sandbox Code Playgroud)

该库没有记录,但下面是我在 .NET 6 + EF Core 6 + Npgsql 6 中使用它的情况

public class DbResult
{
    public string Name { get; set; }
    public int Age { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
using EntityFrameworkCore.RawSQLExtensions.Extensions;
Run Code Online (Sandbox Code Playgroud)
var results = await context.Database
    .SqlQuery<DbResult>(
        @"select name, age from ""users"" where age > @Age",
        new NpgsqlParameter("@Age", 15))
    .ToListAsync();
Run Code Online (Sandbox Code Playgroud)


Lap*_*mir 5

我使用Dapper来绕过 Entity Framework Core 的这个限制。

IDbConnection.Query
Run Code Online (Sandbox Code Playgroud)

正在使用具有多个参数的 SQL 查询或存储过程。顺便说一句,它更快一点(请参阅基准测试

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();
        }


        }
}
Run Code Online (Sandbox Code Playgroud)