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?

pitaridis
  • 2,801
  • 3
  • 22
  • 41
  • 2
    Not yet. Eventually will be possible in some future EFC release. See [Raw store access APIs: Support for ad hoc mapping of arbitrary types #1862](https://github.com/aspnet/EntityFrameworkCore/issues/1862) and [View types support #9290](https://github.com/aspnet/EntityFrameworkCore/issues/9290). – Ivan Stoev Jan 16 '18 at 10:16
  • Thank you for your reply. So I have to wait for this future. – pitaridis Jan 16 '18 at 10:48
  • More on this feature here: https://msdn.microsoft.com/en-us/magazine/mt847184.aspx – Gert Arnold Jul 11 '18 at 21:03

4 Answers4

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

Martín
  • 3,105
  • 7
  • 25
  • 43
  • 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. :-S – JP Hellemons Aug 01 '18 at 09:18
  • Had to revise my query but excellent job Martin! I can confirm and so can this [thread](https://github.com/aspnet/EntityFrameworkCore/issues/11624). – petrosmm Oct 25 '19 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 Jul 27 '20 at 06:31
  • 1
    This is outdated apparently, and it is recommended to use `modelBuilder.Entity().HasNoKey()`. – BCdotWEB Jan 12 '22 at 16:05
  • It is way simpler and more practical to just use dapper for this kind of scenario. – Pepe Alvarez Nov 11 '22 at 21:58
12

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
mn.
  • 796
  • 6
  • 12
7

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 ......");
pitaridis
  • 2,801
  • 3
  • 22
  • 41
  • 2
    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. – Gert Arnold Jul 12 '18 at 07:14
  • Adding generic Activator.CreateInstance() 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). – Milan Vidakovic Jul 12 '18 at 07: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 Jul 12 '18 at 09: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. – Roma Borodov Jul 07 '19 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;
}
Daniele
  • 191
  • 1
  • 4