17

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?

askedJan 16, 2018 at 9:44
pitaridis's user avatar
3

4 Answers4

18

From .NET Core 2.1:

  1. AddmodelBuilder.Query<YourModel>() toOnModelCreating(ModelBuilder modelBuilder)

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

answeredJun 26, 2018 at 4:12
Martín's user avatar
Sign up to request clarification or add additional context in comments.

5 Comments

Thank you, but wouldn't this result in tables in a database being generated based onYourModel? 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. :-S
Had to revise my query but excellent job Martin! I can confirm and so can thisthread.
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 ...
This is outdated apparently, and it is recommended to usemodelBuilder.Entity<YourModel>().HasNoKey().
It is way simpler and more practical to just use dapper for this kind of scenario.
15

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 subclassusing (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 useFromSqlInterpolated instead ofFromSqlRaw
  • 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
answeredFeb 27, 2020 at 10:59
mn.'s user avatar

2 Comments

Hi may i know where to write the final part using (var db = new DbContext())
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.
10

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 ......");
answeredJul 12, 2018 at 5:56
pitaridis's user avatar

4 Comments

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.
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).
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.
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.
5

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

CreateDbSet 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;}
answeredSep 7, 2020 at 3:23
Daniele's user avatar

1 Comment

Hi may I know where to write the dbContext instance

Your Answer

Sign up orlog in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to ourterms of service and acknowledge you have read ourprivacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.