1

I am trying to figure out how to make my DbSet initialization come from a custom query.

Explanations:

My DbSet will be read-only.

Lets say I have 3 tables in my Database. I wrote an SQL query that makes complex selection on the 3 tables. Now I want to use Entity Framework to query on the results of the query.

I think this is possible by creating a thing like this :

class MyCustomContext : DbContext
{
    public MyCustomContext(string connectionString) 
        : base(connectionString)
    {

    }

    public DbSet<MyQueryResultRow> ResultsRows { get; set; }
}

But I don't know how to say to the DbContext "Hey, for retrieving the rows of that DbSet, use that sql query!".

NB : I can't and I don't want to modify the database (I can't create a sql view for example).

Any idea?

DavidG
  • 113,891
  • 12
  • 217
  • 223
Floc
  • 668
  • 8
  • 16
  • 1
    http://stackoverflow.com/questions/10898344/execute-custom-sql-with-entity-framework – MajkeloDev Oct 06 '15 at 08:45
  • It don't seems to answer my question. I really want to use Entity Framework DataSet because I will need to make some OrderBy / Take / Skip etc. and I want that Entity Framework do this in the database side. I don't want to fetch all the data **then** make Linq on it. – Floc Oct 06 '15 at 08:53
  • Why can't you just add 3 `DbSet` properties into your context (one for each table) and join them with Linq? – DavidG Oct 06 '15 at 08:57
  • DbSet is *not* a dataset - it's what allows you to query against an entity. Results are *not* placed in a DbSet, they are simply objects. You don't need it either to execute queries. What do you want to do? What type of query are you trying to execute? – Panagiotis Kanavos Oct 06 '15 at 08:58
  • @DavidG in an ORM the correct way is to create relations between objects, *not* use joins when querying. Not much point in using them otherwise – Panagiotis Kanavos Oct 06 '15 at 08:59
  • PS: If the query is complex, the right answer *is* to create a view. Maintaining raw SQL strings in code is not fun – Panagiotis Kanavos Oct 06 '15 at 09:00
  • @PanagiotisKanavos That's what I want, create a view and query it with EntityFramework / Linq. But I can't create a view on the database. So my idea was to make a view-like but in code. Is that possible ? – Floc Oct 06 '15 at 09:03
  • @PanagiotisKanavos "correct way" is a very subjective term and not always possible to do. – DavidG Oct 06 '15 at 09:04
  • @DavidG raw SQL is a bad choice and that's not subjective. DALs and ORMs were created to *hide* or *remove* raw SQL. Joins instead of relations is also mostly bad - it means the mapping is broken unless a relation can't be created – Panagiotis Kanavos Oct 06 '15 at 09:10
  • @PanagiotisKanavos I did not suggest raw SQL, nor would I except in extreme circumstances. – DavidG Oct 06 '15 at 09:10
  • @Floc *What* is the query you want to execute? It's impossible to answer without any information - if you can create relations, you won't have a complex query to begin with. – Panagiotis Kanavos Oct 06 '15 at 09:11
  • @PanagiotisKanavos Also Linq is often needed, for example when working with aggregated data or computed values. It's not always possible to build mappings. – DavidG Oct 06 '15 at 09:14
  • You can query based on just ***entity sets*** and if you want to reuse it, try creating some method returning IQueryable. I don't think you need to use raw sql query to build up a DbSet. – Hopeless Oct 06 '15 at 09:15
  • @PanagiotisKanavos My query is built with a lot of Left join, Max(), Min(), sub request and other things that make it quite impossible to do in code without read all the data of all the tables. – Floc Oct 06 '15 at 09:16
  • 1
    @Floc how do you know that? Left Join is just a 1:N relation, for example. Subqueries are typically just another `.Select().Where()` combination (although performance may be atrocious). *Please post the query*. – Panagiotis Kanavos Oct 06 '15 at 09:19
  • EF will try translating the LInQ query to SQL query without loading redundant data, you can see the generated sql query to check that. The only reason you want to stick to SQL query is you have some existing sql query but you don't want to translate them into LinqToEntity, which may be hard for many who are not familiar with Linq. – Hopeless Oct 06 '15 at 09:20

3 Answers3

0

You can just create your dbContext as normal and then use the SqlQuery for 'non entity' types.

For example:

using (var context = new myDbContext()) 
{ 
    var things = context.Database.SqlQuery<myType>( 
                       "SELECT things FROM dbo.myTables").ToList(); 
}

The key point is using context.Database then you have access to all the tables (that your SQL account has access too, of course!), and you can define your 'myType' to map the columns you're selecting.

Russ Clarke
  • 17,511
  • 4
  • 41
  • 45
  • This should be the last resort - if the query is complex, maintaining a hard-coded SQL string will be difficult. Far better to create the appropriate mappings and relations. For complex queries, a view would be infinitely better – Panagiotis Kanavos Oct 06 '15 at 09:01
  • Quite so, but as he said - " I can't and I don't want to modify the database (I can't create a sql view for example)." – Russ Clarke Oct 06 '15 at 09:05
  • 1
    Finally, I used this answer. I return the context.Database.SqlQuery( "query") then I do some Take / Skip / OrderBy on it and the query is executed after that. That's not the best way to do it but it works. – Floc Oct 06 '15 at 09:45
0

Create a class which represents 1 to 1 what will be returned by the SQL.

This includes the data types.

public class CustomClass
{
    public int Id {get; set;}
    public string Name {get; set;}
    ....
}

//you  can pass in variables to this method...
public List<CustomClass> GetCustomeClass()
{
    //here you just need to ensure what you select matches the class(CustomClass).
    string query = "Select * from Table_XYS";

    List<CustomClass> res = context.Database.SqlQuery<CustomClass>(query).ToList();

    return res;

}

Assuming Table_XYS has Two Fields, Id(int) and Name(nvarchar) then this would work as is.

The main point here is that the SQL query can include joins or group bys, or what ever you want just as long as the Custom Class and the final select from the SQL have the same prop/fields.. including types. then it will bind the data for you.

Seabizkit
  • 2,417
  • 2
  • 15
  • 32
  • Yes, I know that solution but I need to use Linq to Entities on the result of the query. With that solution, I can only use standard Linq. That's not what I want. – Floc Oct 06 '15 at 09:07
  • Yes you can... just filter it in the SQL.. make that the base and return it as IQueriable... or better write the whole thing in Linq off the DBsets... – Seabizkit Oct 06 '15 at 09:17
-1

Maybe you could try adding another layer, e.g. Repository layer:

public class MyCustomRepository
{
    private readonly MyCustomContext _context;
    public MyCustomRepository(MyCustomContext context)
    {
        _context = context;
    }

    public IEnumerable<MyQueryResultRow> GetResultRows()
    {
        return _context.ResultsRows.Where(r => r.Id > 10); // Here you can add some restrictions
    }
}

Returning IEnumerable won't execute query. Only after executing .ToList() method the query would be executed in the db, so you can easily add more clauses :

var myExtendedQueryResults = repository
    .GetResultRows()
    .Skip(5)
    .OrderBy(r => r.Name)
    .Take(10)
    .ToList();
  • You understand the OP's question wrong, he did not even has a property `ResultsRows` ***set-up in the way he wants***. As he said, he wants a query built-up from some custom SQL query. As other answers suggested of using `context.Database.SqlQuery<>(...)`. – Hopeless Oct 06 '15 at 09:09
  • Edit : read too fast. That's what I want but before I need to retrieve the "ResultsRows" without fetching all the records. – Floc Oct 06 '15 at 09:10
  • @Floc from your requirement, it cannot be a solution you can use. Unless you solve your ***original*** problem in some other way (not trying to build a DbSet from custom sql query). – Hopeless Oct 06 '15 at 09:11
  • An ORM *is* one of the components of a Repository. And returning IEnumerable *will* execute the query - you'll have to return an IQueryable to avoid execution. In fact, this answer doesn't show anything different from what LINQ to Entities already does (apart from loading data by accident) – Panagiotis Kanavos Oct 06 '15 at 09:12
  • @PanagiotisKanavos I don't think what you say here is true. Someone would read this and remember what you have written. IEnumerable does not execute the query. For instance see this: http://stackoverflow.com/questions/4061951/does-converting-iqueryable-to-ienumerable-execute-the-query-again It doesn't mean my answer is correct in the scope of the question and I admit I didn't fully understand it :( – Miłosz Wieczorek Oct 06 '15 at 11:11
  • Even if the query isn't executed immediatelly, all the data will have to be loaded as if it were. That's because any operations would be applied to the Enumerable, not the Queryable. EF wouldn't be able to generate a SQL statement with the operators applied after the first conversion to IEnumerable. In your example, if you had 100K rows, you'd load 99990 of them in memory with `GetResultRows()` and then apply Skip, Order etc to the loaded rows. The bug is that you convert the IQueryable to IEnumerable in the first place, not when the statement gets executed – Panagiotis Kanavos Oct 06 '15 at 11:25