Po usunięciu Entity Framework Core dbData.Database.SqlQuery<SomeModel>
nie mogę znaleźć rozwiązania do zbudowania surowego zapytania SQL dla mojego zapytania wyszukiwania pełnotekstowego, które zwróci dane tabel, a także pozycję.
Jedyną metodą, jaką widziałem do tworzenia surowego zapytania SQL w Entity Framework Core, jest metoda, dbData.Product.FromSql("SQL SCRIPT");
która nie jest przydatna, ponieważ nie mam zestawu DbSet, który będzie mapował pozycję zwracaną w zapytaniu.
Jakieś pomysły???
c#
entity-framework-core
David Harlow
źródło
źródło
Odpowiedzi:
To zależy od tego, czy używasz EF Core 2,1 lub EF Core 3 i wyższych wersji .
Jeśli używasz EF Core 2,1
Jeśli używasz EF Core 2.1 Release Candidate 1 dostępnego od 7 maja 2018 r., Możesz skorzystać z proponowanej nowej funkcji, którą jest typ zapytania.
Co to jest typ zapytania ?
Kiedy używać typu zapytania?
Nie musisz więc już wykonywać wszystkich hacków ani obejść proponowanych jako odpowiedzi na Twoje pytanie. Po prostu wykonaj następujące kroki:
Najpierw zdefiniowałeś nową właściwość typu,
DbQuery<T>
gdzieT
jest typem klasy, która będzie zawierać wartości kolumn Twojego zapytania SQL. Więc w swoimDbContext
będziesz miał to:public DbQuery<SomeModel> SomeModels { get; set; }
Po drugie użyj
FromSql
metody takiej jak w przypadkuDbSet<T>
:var result = context.SomeModels.FromSql("SQL_SCRIPT").ToList(); var result = await context.SomeModels.FromSql("SQL_SCRIPT").ToListAsync();
Zauważ również, że
DdContext
są to klasy częściowe , więc możesz utworzyć jeden lub więcej oddzielnych plików, aby uporządkować definicje „surowego SQL DbQuery” tak, jak najbardziej Ci odpowiada.Jeśli używasz EF Core 3,0 i wyższych wersji
Typ zapytania jest teraz znany jako typ jednostki bez kluczy . Jak wspomniano powyżej, typy zapytań zostały wprowadzone w EF Core 2,1. Jeśli używasz EF Core 3,0 lub nowszej wersji, powinieneś teraz używać typów tntity bez klucza, ponieważ typy zapytań są teraz oznaczone jako przestarzałe.
Nadal mamy te same scenariusze, co w przypadku typów zapytań, kiedy należy używać bezkluczowego typu jednostki.
Więc go używać trzeba najpierw zaznaczyć swoją klasę
SomeModel
z[Keyless]
adnotacją danych lub poprzez płynną konfiguracji z.HasNoKey()
wywołania metody jak poniżej:public DbSet<SomeModel> SomeModels { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<SomeModel>().HasNoKey(); }
Po tej konfiguracji możesz użyć jednej z opisanych tutaj metod do wykonania zapytania SQL. Na przykład możesz użyć tego:
var result = context.SomeModels.FromSqlRaw("SQL SCRIPT").ToList();
źródło
bit
)?[NotMapped]
doSomeModels
klasy nie działa dla mnie. Czy coś przegapiłem?DbQuery
używanyDbSet
z typami jednostek bez klucza .modelBuilder.Entity<MyData>().HasNoKey().ToView(null);
@ Jean-Paul Myślę, że to rozwiązuje twój problemOpierając się na innych odpowiedziach, napisałem pomocnika, który wykonuje zadanie, w tym przykładowe użycie:
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; } } } }
Stosowanie:
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}"));
Planuję się go pozbyć, gdy tylko zostanie dodana wbudowana obsługa. Według oświadczenia Arthura Vickersa z zespołu EF Core jest to wysoki priorytet dla postu 2.0. Problem jest śledzony tutaj .
źródło
W EF Core nie można już wykonywać „wolnego” surowego sql. Musisz zdefiniować klasę POCO i
DbSet
dla tej klasy. W twoim przypadku będziesz musiał zdefiniować Rank :var ranks = DbContext.Ranks .FromSql("SQL_SCRIPT OR STORED_PROCEDURE @p0,@p1,...etc", parameters) .AsNoTracking().ToList();
Ponieważ z pewnością będzie to tylko do odczytu, przydatne będzie dołączenie
.AsNoTracking()
wezwania.EDYCJA - przełomowa zmiana w EF Core 3.0:
DbQuery () jest teraz przestarzała, zamiast tego należy użyć DbSet () (ponownie). Jeśli masz jednostkę bezkluczową, czyli nie wymaga ona klucza podstawowego, możesz użyć metody HasNoKey () :
Więcej informacji można znaleźć tutaj
źródło
DbContext
o nową właściwośćDbSet<Rank> Rank { get; set; }
. Jakie konsekwencje będzie to miało teraz w odniesieniu do linq? To znaczyDBContext.Rank.Where(i => i.key == 1)
, czy nie będziemy mogli teraz użyć takiej instrukcji i czy ta instrukcja nie będzie miała implementacji w języku SQL i dlatego zawiedzie?Możesz wykonać surowy plik SQL w EF Core - Dodaj tę klasę do projektu. Umożliwi to wykonanie surowego kodu SQL i uzyskanie surowych wyników bez konieczności definiowania POCO i DBSet. Oryginalny przykład można znaleźć na stronie 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); } } } }
Oto przykład, jak go używać:
// 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]); } }
źródło
Na razie, dopóki nie pojawi się coś nowego z EFCore, użyłem polecenia i zmapowałem to ręcznie
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 } } }
Spróbuj SqlParameter, aby uniknąć iniekcji Sql.
dbData.Product.FromSql("SQL SCRIPT");
FromSql nie działa z pełnym zapytaniem. Przykład, jeśli chcesz dołączyć klauzulę WHERE, zostanie ona zignorowana.
Niektóre linki:
Wykonywanie surowych zapytań SQL przy użyciu Entity Framework Core
Surowe zapytania SQL
źródło
Możesz tego użyć (z 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); } } }
I użycie:
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"); }
źródło
modelBulider.Query<T>()
jest przestarzały w EntityFramework Core 3W Core 2.1 możesz zrobić coś takiego:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Query<Ranks>(); }
a następnie zdefiniuj procedurę SQL, na przykład:
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; }
W ten sposób model rang nie zostanie utworzony w twojej bazie danych.
Teraz w kontrolerze / akcji możesz wywołać:
W ten sposób możesz wywołać Raw SQL Procedures.
źródło
FromSql
można po prostu przekazać bez tworzeniaSqlParameter
obiektu:FromSql($"STORED_PROCEDURE {value1}, {value2}")
lubFromSql("STORED_PROCEDURE {0}, {1}", value1, value2)
(zostaną usunięte).Dodaj pakiet Nuget - Microsoft.EntityFrameworkCore.Relational
using Microsoft.EntityFrameworkCore; ... await YourContext.Database.ExecuteSqlCommandAsync("... @p0, @p1", param1, param2 ..)
To zwróci numery wierszy jako int
Zobacz - https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.executesqlcommand?view=efcore-3.0
źródło
spróbuj tego: (utwórz metodę rozszerzenia)
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; } } }
Stosowanie:
var db = new dbContext(); string query = @"select ID , Name from People where ... "; var lst = db.ExecuteQuery<PeopleView>(query);
mój model: (nie w
DbSet
):public class PeopleView { public int ID { get; set; } public string Name { get; set; } }
Uwaga: to rozwiązanie ma niską wydajność
źródło
Nie jest ukierunkowany bezpośrednio na scenariusz OP, ale ponieważ walczyłem z tym, chciałbym porzucić te ex. metody, które ułatwiają wykonywanie surowego kodu SQL za pomocą
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(); } } }
źródło
Użyłem Dappera, aby ominąć to ograniczenie Entity Framework Core.
działa z zapytaniem sql lub procedurą składowaną z wieloma parametrami. Swoją drogą jest trochę szybszy (zobacz testy porównawcze )
Dapper jest łatwy do nauczenia. Zapisanie i uruchomienie procedury składowanej z parametrami zajęło 15 minut. W każdym razie możesz używać zarówno EF, jak i Dapper. Poniżej przykład:
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(); } } }
źródło
Możesz także użyć QueryFirst . Podobnie jak Dapper, jest to całkowicie poza EF. W przeciwieństwie do Dapper (lub EF), nie musisz utrzymywać POCO, edytujesz SQL SQL w prawdziwym środowisku i jest on stale weryfikowany względem bazy danych. Zastrzeżenie: jestem autorem QueryFirst.
źródło
W moim przypadku zamiast surowego kodu SQL użyto procedury składowanej
Stworzył klasę
Public class School { [Key] public Guid SchoolId { get; set; } public string Name { get; set; } public string Branch { get; set; } public int NumberOfStudents { get; set; } }
Dodano poniżej w mojej
DbContext
klasiepublic DbSet<School> SP_Schools { get; set; }
Aby wykonać procedurę składowaną:
var MySchools = _db.SP_Schools.FromSqlRaw("GetSchools @schoolId, @page, @size ", new SqlParameter("schoolId", schoolId), new SqlParameter("page", page), new SqlParameter("size", size))) .IgnoreQueryFilters();
źródło
Wiem, że to stare pytanie, ale być może pomaga komuś wywołać procedury składowane bez dodawania DTO jako DbSets.
https://stackoverflow.com/a/62058345/3300944
źródło
To rozwiązanie opiera się w dużej mierze na rozwiązaniu firmy @pius. Chciałem dodać opcję obsługi parametrów zapytania, aby pomóc złagodzić wstrzykiwanie SQL, a także chciałem, aby było to rozszerzenie poza DbContext DatabaseFacade dla Entity Framework Core, aby było trochę bardziej zintegrowane.
Najpierw utwórz nową klasę z rozszerzeniem:
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; } } } } }
Zauważ w powyższym, że „T” jest typem zwracanej wartości, a „P” jest typem parametrów zapytania, które będą się różnić w zależności od tego, czy używasz MySql, Sql, itd.
Następnie pokażemy przykład. Używam możliwości MySql EF Core, więc zobaczymy, jak możemy użyć powyższego rozszerzenia ogólnego z tą bardziej szczegółową implementacją 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; } } }
Zapytanie zwróci takie wiersze, jak:
„Ford”, „Explorer”, „Ford Explorer”,
„Tesla”, „Model X”, „Tesla Model X”
Tytuł wyświetlany nie jest zdefiniowany jako kolumna bazy danych, więc domyślnie nie byłby częścią modelu EF Car. Podoba mi się to podejście jako jedno z wielu możliwych rozwiązań. Inne odpowiedzi na tej stronie odnoszą się do innych sposobów rozwiązania tego problemu za pomocą dekoratora [NotMapped], który w zależności od przypadku użycia może być bardziej odpowiednim podejściem.
Zwróć uwagę, że kod w tym przykładzie jest oczywiście bardziej szczegółowy, niż powinien, ale pomyślałem, że dzięki temu przykład jest jaśniejszy.
źródło
Właściwie możesz utworzyć ogólne repozytorium i zrobić coś takiego
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); }
źródło
Z Entity Framework 6 możesz wykonać coś takiego jak poniżej
Public class User { public int Id { get; set; } public string fname { get; set; } public string lname { get; set; } public string username { get; set; } }
Wykonaj surową komendę DQL SQl jak poniżej:
var userList = datacontext.Database.SqlQuery<User>(@"SELECT u.Id ,fname , lname ,username FROM dbo.Users").ToList<User>();
źródło