web-dev-qa-db-fra.com

Requête SQL brute sans DbSet - Entity Framework Core

Avec Entity Framework Core supprimant dbData.Database.SqlQuery<SomeModel>, je ne trouve pas de solution pour créer une requête SQL brute pour ma requête de recherche de texte intégral qui renverra les données des tables ainsi que le classement. 

La seule méthode que j'ai vue pour créer une requête SQL brute dans Entity Framework Core est via dbData.Product.FromSql("SQL SCRIPT");, ce qui n'est pas utile car je n'ai pas de DbSet qui mappera le rang que je retourne dans la requête.

Des idées???

48
David Harlow

Si vous utilisez EF Core 2.1 Release Candidate 1 disponible depuis le 7 mai 2018, vous pouvez tirer parti de la nouvelle fonctionnalité proposée, à savoir le type de requête.

Quel est type de requête ?

Outre les types d'entité, un modèle EF Core peut contenir des types de requête, qui peut être utilisé pour effectuer des requêtes de base de données contre des données qui n'est pas mappé aux types d'entité.

Quand utiliser le type de requête?

Servir comme type de retour pour les requêtes ad hoc FromSql ().

Mappage aux vues de base de données.

Mappage sur des tables pour lesquelles aucune clé primaire n'est définie.

Correspondance aux requêtes définies dans le modèle.

Vous n’avez donc plus besoin de faire toutes les astuces ou solutions de contournement proposées pour répondre à votre question. Il suffit de suivre ces étapes:

Vous avez d’abord défini une nouvelle propriété de type DbQuery<T>T est le type de la classe qui portera les valeurs de colonne de votre requête SQL. Donc, dans votre DbContext, vous aurez ceci:

public DbQuery<SomeModel> SomeModels { get; set; }

Deuxièmement, utilisez la méthode FromSql comme vous le faites avec DbSet<T>:

var result = context.SomeModels.FromSql("SQL_SCRIPT").ToList();
var result = await context.SomeModels.FromSql("SQL_SCRIPT").ToListAsync();

Notez également que les DBContexts sont des classes partielles , afin que vous puissiez créer un ou plusieurs fichiers séparés pour organiser vos définitions de 'données SQL brutes DbQuery' comme il vous convient.

41
CodeNotFound

Dans EF Core, vous ne pouvez plus exécuter de sql brut "gratuit". Vous devez définir une classe POCO et une DbSet pour cette classe . Dans votre cas, vous devrez définir Rank :

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

Comme ce sera sûrement en lecture seule, il sera utile d'inclure l'appel .AsNoTracking().

24
E-Bat

Vous pouvez exécuter sql brut dans EF Core - Ajouter cette classe à votre projet . Cela vous permettra d’exécuter du SQL brut et d’obtenir les résultats bruts sans avoir à définir un POCO et un DBSet . Voir https: //github.com/aspnet/EntityFramework/issues/1862#issuecomment-220787464 pour l'exemple original.

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

Voici un exemple d'utilisation:

// 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]);
    }
}
15
Yehuda Goldenberg

En me basant sur les autres réponses, j'ai écrit cette aide qui accomplit la tâche, avec un exemple d'utilisation:

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

Usage:

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

Je prévois de m'en débarrasser dès que le support intégré sera ajouté. Selon un statement d'Arthur Vickers de l'équipe EF Core, il s'agit d'une priorité élevée pour la version 2.0. La question est en cours de suivi ici .

12
pius

Pour l'instant, jusqu'à ce qu'il y ait quelque chose de nouveau chez EFCore, j'utilisais une commande .__ et le mappais manuellement

  using (var command = this.DbContext.Database.GetDbConnection().CreateCommand())
        {
            command.CommandText = "SELECT ... WHERE ...> @p1)";
            command.CommandType = CommandType.Text;
            var parameter = new SqlParameter("@p1",...);

            this.DbContext.Database.OpenConnection();

            using (var result = command.ExecuteReader())
            {
                while (result.Read())
                {
                   .... // Map to your entity
                }
            }
        }

Essayez SqlParameter pour éviter Sql Injection.

       dbData.Product.FromSql("SQL SCRIPT");

FromSql ne fonctionne pas avec une requête complète. Exemple si vous souhaitez inclure une clause WHERE, celle-ci sera ignorée.

Quelques liens:

Exécution de requêtes SQL brutes avec Entity Framework Core

Requêtes SQL brutes

3
Henry

Vous pouvez utiliser ceci (à partir de 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);
        }
    }
}
3
ErikEJ

Dans Core 2.1, vous pouvez faire quelque chose comme ceci:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
       modelBuilder.Query<Ranks>();
}

puis définissez votre procédure SQL, comme:

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

De cette façon, le modèle Ranks ne sera pas créé dans votre base de données.

Maintenant, dans votre contrôleur/action, vous pouvez appeler:

List<Ranks> gettingRanks = _DbContext.GetRanks(value1,value2).Result.ToListAsync();

De cette façon, vous pouvez appeler des procédures SQL brutes.

3
RodrigoCampos

J'ai utilisé Dapper pour contourner cette contrainte du framework Entity Core. 

IDbConnection.Query

travaille avec une requête SQL ou une procédure stockée avec plusieurs paramètres ..__ En passant, c'est un peu plus rapide (voir tests de performances )

Dapper est facile à apprendre. Il a fallu 15 minutes pour écrire et exécuter une procédure stockée avec des paramètres. Quoi qu'il en soit, vous pouvez utiliser EF et Dapper. En voici un exemple:

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


        }
}
0
Lapenkov Vladimir

Vous pouvez également utiliser QueryFirst . Comme Dapper, ceci est totalement extérieur à EF. Contrairement à Dapper (ou EF), vous n'avez pas besoin de gérer le POCO, vous éditez votre SQL SQL dans un environnement réel et il est constamment revalidé par rapport à la base de données. Disclaimer: Je suis l'auteur de QueryFirst.

0
bbsimonbb

Ne pas cibler directement le scénario du PO, mais depuis que je lutte avec cela, j'aimerais laisser tomber ces ex. méthodes qui facilitent l’exécution de SQL brute avec la variable DbContext:

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

Dans le cas où vous souhaitez uniquement exécuter la requête et souhaitez une valeur int. EFCore 2 a la méthode DbContext.Database.ExecuteSqlCommand("Yourqueryhere").

Modifier:

Les ExecuteSqlCommand et ExecuteSqlCommandAsync sont définis dans l'espace de noms Microsoft.EntityFrameworkCore.Relational. Assurez-vous qu'il est référencé. 

0
Mohsin