Raw SQL-Abfrage ohne DbSet - Entity Framework Core

106

Wenn Entity Framework Core entfernt wird, dbData.Database.SqlQuery<SomeModel>kann ich keine Lösung finden, um eine unformatierte SQL-Abfrage für meine Volltextsuchabfrage zu erstellen, die die Tabellendaten und auch den Rang zurückgibt.

Die einzige Methode, die ich gesehen habe, um eine unformatierte SQL-Abfrage in Entity Framework Core zu erstellen, ist eine, dbData.Product.FromSql("SQL SCRIPT");die nicht nützlich ist, da ich kein DbSet habe, das den Rang abbildet, den ich in der Abfrage zurückgebe.

Irgendwelche Ideen???

David Harlow
quelle
15
Ich werde die SqlQuery <T> sehr vermissen und möchte meinem DbContext keine benutzerdefinierten Klassen zuordnen müssen, wenn ich wirklich nur ein einfaches DTO für einen bestimmten Anwendungsfall benötige. Ich habe eine Benutzerstimme erstellt, um das Hinzufügen dieser Funktion zu EF Core anzufordern, damit jeder abstimmen kann, wenn er diese Funktion wieder haben möchte
Matt Sanders
1
Laut github.com/aspnet/EntityFramework/issues/1862 ist dies jetzt auf EF Core 1.2 und / oder 1.1.0-Vorschau1
Dan Field
Ein wichtiger Hinweis: FromSql und ExecuteSqlCommand sind Erweiterungsmethoden. Sie sind in DbSet oder DatabaseFacade nicht vorhanden. Sie müssen daher sicherstellen, dass der Namespace Microsoft.EntityFrameworkCore importiert wird.
Devon
2
Aufbauend auf dem, was @Devon gerade gesagt hat, habe ich viel zu lange damit verbracht herauszufinden, dass es sich um Erweiterungsmethoden in Microsoft.EntityFrameworkCore.SqlServer handelt. Sie müssen dies Ihrem Projekt hinzufügen, bevor Sie diese Erweiterungsmethoden erhalten.
Daniel
3
Seufz, das scheint eine Art Architektur-Astronauten-Entscheidung zu sein: "Die Leute sollten das nicht wollen müssen". Ich denke, ich muss Dapper nur für diesen Fall installieren. Nervig.
Dirk Boer

Antworten:

124

Dies hängt davon ab, ob Sie EF Core 2.1 oder EF Core 3 und höhere Versionen verwenden .

Wenn Sie EF Core 2.1 verwenden

Wenn Sie EF Core 2.1 Release Candidate 1 verwenden, das seit dem 7. Mai 2018 verfügbar ist, können Sie die vorgeschlagene neue Funktion vom Typ Abfrage nutzen.

Was ist der Abfragetyp ?

Zusätzlich zu Entitätstypen kann ein EF Core-Modell Abfragetypen enthalten, mit denen Datenbankabfragen für Daten ausgeführt werden können, die nicht Entitätstypen zugeordnet sind.

Wann wird der Abfragetyp verwendet?

Dient als Rückgabetyp für Ad-hoc-FromSql () -Abfragen.

Zuordnung zu Datenbankansichten.

Zuordnung zu Tabellen, für die kein Primärschlüssel definiert ist.

Zuordnung zu im Modell definierten Abfragen.

Sie müssen also nicht mehr alle Hacks oder Workarounds ausführen, die als Antwort auf Ihre Frage vorgeschlagen wurden. Befolgen Sie einfach diese Schritte:

Zuerst haben Sie eine neue Eigenschaft vom Typ definiert, DbQuery<T>wobei Tes sich um den Typ der Klasse handelt, die die Spaltenwerte Ihrer SQL-Abfrage enthält. Also in deinem DbContexthast du folgendes:

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

Verwenden FromSqlSie zweitens die Methode wie folgt DbSet<T>:

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

Beachten Sie auch , dass DdContexts sind Teil-Klassen , so dass Sie eine oder mehrere separate Dateien erstellen können Ihre ‚raw SQL dbquery‘ Definitionen , wie am besten zu Ihnen passt zu organisieren.


Wenn Sie EF Core 3.0 und höhere Versionen verwenden

Der Abfragetyp wird jetzt als schlüsselloser Entitätstyp bezeichnet . Wie oben erwähnt, wurden Abfragetypen in EF Core 2.1 eingeführt. Wenn Sie EF Core 3.0 oder eine höhere Version verwenden, sollten Sie jetzt die Verwendung von Keyless-Tntity-Typen in Betracht ziehen, da Abfragetypen jetzt als veraltet markiert sind.

Diese Funktion wurde in EF Core 2.1 unter dem Namen Abfragetypen hinzugefügt. In EF Core 3.0 wurde das Konzept in schlüssellose Entitätstypen umbenannt. Die [Keyless] Data Annotation wurde in EFCore 5.0 verfügbar.

Wir haben immer noch die gleichen Szenarien wie für Abfragetypen, wenn ein schlüsselloser Entitätstyp verwendet werden soll.

Um es zu verwenden, müssen Sie Ihre Klasse zuerst SomeModelmit [Keyless]Datenanmerkungen oder durch fließende Konfiguration mit einem .HasNoKey()Methodenaufruf wie folgt markieren :

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

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<SomeModel>().HasNoKey();
}

Nach dieser Konfiguration können Sie eine der hier erläuterten Methoden verwenden, um Ihre SQL-Abfrage auszuführen. Zum Beispiel können Sie dieses verwenden:

var result = context.SomeModels.FromSqlRaw("SQL SCRIPT").ToList();
CodeNotFound
quelle
18
Diese Antwort sollte die beste Lösung sein, wenn Sie EF Core 2.1 und höher verwenden. 👍
Will Huang
2
@CodeNotFound Was ist, wenn ich das Ergebnis nicht benötige oder wenn es ein primitiver Typ ist (zum Beispiel bit)?
Shimmy Weitzhandler
5
Mit CodeFirst wurde automatisch eine Tabelle mit all diesen Eigenschaften erstellt. Das Hinzufügen [NotMapped]zur SomeModelsKlasse funktioniert bei mir nicht. Habe ich etwas vergessen?
Jean-Paul
7
EF Core 3.0 wird nicht mehr DbQuerynur DbSetfür schlüssellose Entitätstypen verwendet .
NetMage
3
Nur zu Ihrer Information: Aufgrund eines Fehlers in EF Core 3.0 wird bei einer Code-First-Migration auch bei mit HasNoKey () gekennzeichneten Entitäten weiterhin versucht, eine Tabelle zu erstellen. Sie müssen also auch .ToView (null) hinzufügen. ZB modelBuilder.Entity<MyData>().HasNoKey().ToView(null);@ Jean-Paul Ich denke, das löst dein Problem
stann1
34

Aufbauend auf den anderen Antworten habe ich diesen Helfer geschrieben, der die Aufgabe erfüllt, einschließlich der Verwendung von Beispielen:

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

Verwendung:

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

Ich habe vor, es loszuwerden, sobald die integrierte Unterstützung hinzugefügt wird. Laut einer Aussage von Arthur Vickers vom EF Core-Team hat dies für Post 2.0 eine hohe Priorität. Das Problem wird hier verfolgt .

Pius
quelle
31

In EF Core können Sie "free" raw sql nicht mehr ausführen. Sie müssen eine POCO-Klasse und eine DbSetfür diese Klasse definieren. In Ihrem Fall müssen Sie Rang definieren :

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

Da es sicherlich nur lesbar sein wird, ist es nützlich, den .AsNoTracking()Anruf einzuschließen .

EDIT - Breaking Change in EF Core 3.0:

DbQuery () ist jetzt veraltet, stattdessen sollte DbSet () (erneut) verwendet werden. Wenn Sie eine schlüssellose Entität haben, dh keinen Primärschlüssel benötigen, können Sie die HasNoKey () -Methode verwenden:

ModelBuilder.Entity<SomeModel>().HasNoKey()

Weitere Informationen finden Sie hier

E-Bat
quelle
3
Ich denke, ich muss das auch erweitern DbContext, um eine neue Eigenschaft aufzunehmen DbSet<Rank> Rank { get; set; }. Welche Auswirkungen wird dies nun in Bezug auf linq haben? Dh können wir jetzt keine Anweisung wie verwenden DBContext.Rank.Where(i => i.key == 1), und wird diese Anweisung nicht in SQL implementiert und schlägt daher fehl?
David Harlow
Linq, das gegen diesen Satz ausgegeben wird, muss im Speicher aufgelöst werden. Wenn Sie eine andere WHERE-SQL-Klausel ausgeben müssen, müssen Sie diese als Parameter einschließen oder ein anderes Skript erstellen.
E-Bat
Mein DbSet hat keine "FromSql" -Methode. Ist das eine Erweiterung, die mir fehlt?
Birwin
1
@birwin, müssen Sie den Namespace Microsoft.EntityFrameworkCore
E-Bat
20

Sie können Raw-SQL in EF Core ausführen - Fügen Sie diese Klasse Ihrem Projekt hinzu. Auf diese Weise können Sie unformatiertes SQL ausführen und die rohen Ergebnisse abrufen, ohne ein POCO und ein DBSet definieren zu müssen. Ein Originalbeispiel finden Sie unter 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);
            }
        }
    }
}

Hier ist ein Beispiel für die Verwendung:

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

Bis es etwas Neues von EFCore gibt, würde ich einen Befehl verwenden und ihn manuell zuordnen

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

Versuchen Sie, SqlParameter zu verwenden, um eine SQL-Injektion zu vermeiden.

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

FromSql funktioniert nicht mit vollständiger Abfrage. Beispiel: Wenn Sie eine WHERE-Klausel einfügen möchten, wird diese ignoriert.

Einige Links:

Ausführen von Raw SQL-Abfragen mit Entity Framework Core

Rohe SQL-Abfragen

Henry
quelle
7

In Core 2.1 können Sie Folgendes tun:

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

und definieren Sie dann Ihre SQL-Prozedur wie:

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

Auf diese Weise wird in Ihrer Datenbank kein Ranks-Modell erstellt.

Jetzt können Sie in Ihrem Controller / Ihrer Aktion Folgendes aufrufen:

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

Auf diese Weise können Sie Raw SQL-Prozeduren aufrufen.

RodrigoCampos
quelle
Die FromSqlParameter können einfach übergeben werden, ohne ein SqlParameterObjekt zu erstellen : FromSql($"STORED_PROCEDURE {value1}, {value2}")oder FromSql("STORED_PROCEDURE {0}, {1}", value1, value2)(sie werden maskiert ).
Majid
7

Sie können dies verwenden (von 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);
        }
    }
}

Und die Verwendung:

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

Nuget-Paket hinzufügen - Microsoft.EntityFrameworkCore.Relational

using Microsoft.EntityFrameworkCore;
...
await YourContext.Database.ExecuteSqlCommandAsync("... @p0, @p1", param1, param2 ..)

Dies gibt die Zeilennummern als int zurück

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

Mohsin
quelle
3
Bitte beachten Sie, dass dies nur die Anzahl der vom Befehl betroffenen Zeilen zurückgibt
kalyfe
Genau das, was ich brauche. Ich verwende Microsoft.EntityFrameworkCore 3.1.1 und kann keine RAW-Abfrage und SP ausführen. Vielen Dank dafür!
Jaysonragasa
4

Versuchen Sie Folgendes: (Erweiterungsmethode erstellen)

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

Verwendung:

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

mein Modell: (nicht in DbSet):

public class PeopleView
{
    public int ID { get; set; }
    public string Name { get; set; }
}

getestet in .netCore 2.2 and 3.0.

Hinweis: Diese Lösung weist eine langsame Leistung auf

AminRostami
quelle
Versuchen Sie, PropertyInfo nur einmal nach Namen für einen ersten Datensatz zu durchsuchen und ein Array von PropertyInfo [] nach Spaltenindizes zu erstellen, um es für die nächsten Datensätze zu verwenden.
Petr Voborník
2

Ich ziele nicht direkt auf das OP-Szenario ab, aber da ich damit zu kämpfen habe, möchte ich diese Ex fallen lassen. Methoden, die es einfacher machen, Raw SQL auszuführen mit 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();
    }
  }
}
Shimmy Weitzhandler
quelle
1

Ich habe Dapper verwendet , um diese Einschränkung des Entity Framework Core zu umgehen.

IDbConnection.Query

arbeitet entweder mit einer SQL-Abfrage oder einer gespeicherten Prozedur mit mehreren Parametern. Übrigens ist es etwas schneller (siehe Benchmark-Tests )

Dapper ist leicht zu lernen. Das Schreiben und Ausführen der gespeicherten Prozedur mit Parametern dauerte 15 Minuten. Auf jeden Fall können Sie sowohl EF als auch Dapper verwenden. Unten ist ein Beispiel:

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


        }
}
Lapenkov Vladimir
quelle
0

Sie können auch QueryFirst verwenden . Wie Dapper liegt dies völlig außerhalb von EF. Im Gegensatz zu Dapper (oder EF) müssen Sie den POCO nicht warten, Sie bearbeiten Ihr SQL-SQL in einer realen Umgebung und es wird gegenüber der Datenbank kontinuierlich neu validiert. Haftungsausschluss: Ich bin der Autor von QueryFirst.

bbsimonbb
quelle
0

In meinem Fall wurde eine gespeicherte Prozedur anstelle von unformatiertem SQL verwendet

Erstellt eine Klasse

Public class School
{
    [Key]
    public Guid SchoolId { get; set; }
    public string Name { get; set; }
    public string Branch { get; set; }
    public int NumberOfStudents  { get; set; }
}

Unten in meiner DbContextKlasse hinzugefügt

public DbSet<School> SP_Schools { get; set; }

So führen Sie die gespeicherte Prozedur aus:

var MySchools = _db.SP_Schools.FromSqlRaw("GetSchools @schoolId, @page, @size ",
              new SqlParameter("schoolId", schoolId),
              new SqlParameter("page", page),
              new SqlParameter("size", size)))
.IgnoreQueryFilters();
NoloMokgosi
quelle
0

Ich weiß, dass es eine alte Frage ist, aber vielleicht hilft es jemandem, gespeicherte Prozeduren aufzurufen, ohne DTOs als DbSets hinzuzufügen.

https://stackoverflow.com/a/62058345/3300944

Andrei
quelle
0

Diese Lösung stützt sich stark auf die Lösung von @pius. Ich wollte die Option zur Unterstützung von Abfrageparametern hinzufügen, um die SQL-Injection zu verringern, und ich wollte es auch zu einer Erweiterung des DbContext DatabaseFacade für Entity Framework Core machen, um es ein wenig integrierter zu machen.

Erstellen Sie zunächst eine neue Klasse mit der Erweiterung:

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

}

Beachten Sie oben, dass "T" der Typ für die Rückgabe ist und "P" der Typ Ihrer Abfrageparameter ist, der abhängig davon variiert, ob Sie MySql, Sql usw. verwenden.

Als nächstes zeigen wir ein Beispiel. Ich verwende die MySql EF Core-Funktion, daher werden wir sehen, wie wir die oben genannte generische Erweiterung mit dieser spezifischeren MySql-Implementierung verwenden können:

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

Die Abfrage würde Zeilen wie
"Ford", "Explorer", "Ford Explorer",
"Tesla", "Modell X", "Tesla-Modell X" zurückgeben.

Der Anzeigetitel ist nicht als Datenbankspalte definiert, sodass er standardmäßig nicht Teil des EF Car-Modells ist. Ich mag diesen Ansatz als eine von vielen möglichen Lösungen. Die anderen Antworten auf dieser Seite verweisen auf andere Möglichkeiten, um dieses Problem mit dem Dekorator [NotMapped] zu beheben. Dies kann je nach Anwendungsfall der geeignetere Ansatz sein.

Beachten Sie, dass der Code in diesem Beispiel offensichtlich ausführlicher ist, als er sein muss, aber ich dachte, er macht das Beispiel klarer.

dan-iel
quelle
-6

Mit Entity Framework 6 können Sie Folgendes ausführen

Modale Klasse erstellen als

Public class User
{
        public int Id { get; set; }
        public string fname { get; set; }
        public string lname { get; set; }
        public string username { get; set; }
}

Führen Sie den Raw DQL SQl-Befehl wie folgt aus:

var userList = datacontext.Database.SqlQuery<User>(@"SELECT u.Id ,fname , lname ,username FROM dbo.Users").ToList<User>();
Siddhartha
quelle