16

Using Entity Framework 6, I was able to use execute a Raw SQL Query and use a custom model which was not defined in the DBContext in order to store the output of the query. A simple example is the following:

List<MyModel> data = context.Database.SqlQuery<MyModel>("SELECT Orders.OrderID, Customers.CustomerName FROM Orders INNER JOIN Customers ON Orders.CustomerID=Customers.CustomerID;").ToList(); 

I execute one SQL command and I expect a list of custom models.

I try to do something similar with Entity Framework Core and the closest example that I found will force me to define a property from DBContext. This will not allow me to use a custom model to fill the data that SQL server will return.

var books = context.Books.FromSql("SELECT * FROM Books").ToList(); 

This query informs Entity Framework Core that the query will return a list of books. Is there a way to implement something like this in Entity Framework Core?

3

4 Answers 4

18

From .NET Core 2.1:

  1. Add modelBuilder.Query<YourModel>() to OnModelCreating(ModelBuilder modelBuilder)

  2. Use context.Query<YourModel>().FromSql(rawSql) to get data

5
  • 1
    Thank you, but wouldn't this result in tables in a database being generated based on YourModel? I have a custom class with productname and quantity. Used the same technique like in the asked question which worked in EF5.x. I have EF core in a .Net standard 2.0 class lib. :-SCommentedAug 1, 2018 at 9:18
  • Had to revise my query but excellent job Martin! I can confirm and so can this thread.
    – petrosmm
    CommentedOct 25, 2019 at 20:37
  • 1
    You need to add this to your model? I guess i'll be using dapper then ... There are often cases where you just want to select a subset of the fields in a custom sql query. This should be possible without adding it to your model ...
    – Davy
    CommentedJul 27, 2020 at 6:31
  • 1
    This is outdated apparently, and it is recommended to use modelBuilder.Entity<YourModel>().HasNoKey().
    – BCdotWEB
    CommentedJan 12, 2022 at 16:05
  • It is way simpler and more practical to just use dapper for this kind of scenario.CommentedNov 11, 2022 at 21:58
14

Here's how I was able to get this working (for completeness):

MyModel.cs:

public class MyModel { // The columns your SQL will return public double? A { get; set; } public double? B { get; set; } } 

Add class that just inherits from your original EF context class (i called mine DbContextBase):

public class DbContext : DbContextBase { public virtual DbSet<MyModel> MyModels { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Necessary, since our model isnt a EF model modelBuilder.Entity<MyModel>(entity => { entity.HasNoKey(); }); } } 

Use that class (instead of your original EF context class):

// Use your new db subclass using (var db = new DbContext()) { var models = await db.MyModels.FromSqlRaw(...).ToListAsync(); // E.g.: "SELECT * FROM apple A JOIN banana B ON A.col = B.col" } 

Notes:

  • If you need to, just use FromSqlInterpolated instead of FromSqlRaw
  • The "db context" subclass allows you to update EF models without affecting your "polyfill" code
  • Works with SQL Server stored procs that return only 1 result set
2
  • Hi may i know where to write the final part using (var db = new DbContext())CommentedJun 26, 2021 at 2:14
  • 2
    Ok I found the answer for that. Just in case if any one wanted to know you have to create it in the controller class file.CommentedJun 26, 2021 at 5:02
9

The question was about .NET Core 2. Now I have a solution and I am going to write it here so that someone else could use it in case he/she needs it.

First of all we add the following method in dbContext class

public List<T> ExecSQL<T>(string query) { using (var command = Database.GetDbConnection().CreateCommand()) { command.CommandText = query; command.CommandType = CommandType.Text; Database.OpenConnection(); List<T> list = new List<T>(); using (var result = command.ExecuteReader()) { T obj = default(T); while (result.Read()) { obj = Activator.CreateInstance<T>(); foreach (PropertyInfo prop in obj.GetType().GetProperties()) { if (!object.Equals(result[prop.Name], DBNull.Value)) { prop.SetValue(obj, result[prop.Name], null); } } list.Add(obj); } } Database.CloseConnection(); return list; } } 

Now we can have the following code.

List<Customer> Customers = _context.ExecSQL<Customer>("SELECT ......"); 
4
  • 3
    This is just plain ADO.Net. Not that it doesn't work, but I wonder why you come up with it while EF core has a built-in solution now.CommentedJul 12, 2018 at 7:14
  • Adding generic Activator.CreateInstance<T>() is terribly slow, and will compound rapidly if querying a large data set. If performance is something you're after, either look at Dapper for a complete solution, or manually create model instances from DbDataReader (not pretty).CommentedJul 12, 2018 at 7:24
  • The entire application is based on EF so I have to use EF. I know the problem with large number of records but we use paging in all our queries so there is no posibility to have more than 100 records.
    – pitaridis
    CommentedJul 12, 2018 at 9:47
  • 2
    The answer doesnt deserve downvote=) Basically there is legit lack of custom query support in EF. core, we f.e. have external views in our architecture on which we are executing joins and custom queries, we cannot just add custom DbSet, since its not supported, so we are using something simular, just with automapper and better caching solution. The problem with this solution is lack of caching, like getProperties is pretty heavy and you could replace ActivatorCreateInstance with compiled expression. AutoMapper would ve basically make all dirty work.CommentedJul 7, 2019 at 10:25
4

follow these steps:

Create your model

Probably it could be better if you can reduce it to a model as generic as possible but it's not a must:

public class MyCustomModel { public string Text { get; set; } public int Count { get; set; } } 

Add it to your own DbContext

Create DbSet for your custom model

public virtual DbSet<MyCustomModel> MyCustomModelName { get; set; } 

Keep in mind to specify your custom model has no key

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

Use it from your dbContext instance

async public Task<List<MyCustomModel>> GetMyCustomData() { var rv = new List<MyCustomModel>(); using (var dataContext = new DbContext()) { var sql = @" select textField as 'Text', count(1) as 'Count' from MyTable"; rv = await dataContext.Set<MyCustomModel>().FromSqlRaw(sql).ToListAsync(); } return rv; } 
1
  • Hi may I know where to write the dbContext instanceCommentedJun 26, 2021 at 2:15

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.