1

I want make generic class to make query in sql database. My class is like that

public class GenericQuery<T>{
   private SomeContext context;
   GenericQuery(SomeContext _context){
      this.context = _context;
   }
   public T GetItemById(int id){
        Task<T> result = Task.Run(() => GetItemByIdAsync(id));
        result.Wait();
        return result.Result;
   }
   private T GetItemByIdAsync(int id){
       var typeName = x // x will extracted from type T using reflection.
       var procName = $"Get{x}"
       T result = this.context.Query<T>().FromSql("{0}, {1}",procName, id).FirstOrDefault();
       return result;
   }
}

But here in method GetItemByIdAsync, this.context.Query<T>().FromSql(---) is giving compilation Error as following -

**Error CS0452  The type 'T' must be a reference type in order to use it as parameter 'TQuery' in the generic type or method 'methodName'**

how solve that?

Shakibuz_Zaman
  • 260
  • 1
  • 14
  • "I want make generic class to make query in sql database" - **don't do this** - you're just recreating the repository anti-pattern which is not needed in Entity Framework. – Dai Jul 07 '20 at 05:32
  • Any better solution then? Because for this reason i have to keep repeating same code again and again for different model just changing the model only. – Shakibuz_Zaman Jul 07 '20 at 05:40
  • Please see my posted answer. – Dai Jul 07 '20 at 05:41
  • If you have any questions about my answer please post them as comment replies to my answer, not your question. – Dai Jul 07 '20 at 05:42
  • Sorry, the answer was collapsed. Didn't notice then. – Shakibuz_Zaman Jul 07 '20 at 05:43

1 Answers1

3
  • If you're using Entity Framework (including Entity Framework Core) then it is an anti-pattern to define a "Repository" or "Generic Repository" type in your project.

    • This also applies to most other ORMs like NHibernate and even the older Linq-to-Sql system: the ORM provides the Repository for you already.
    • Specifically, your DbContext subclass is the Unit-of-Work object.
    • ...and the Entity Framework-owned DbSet<T> class is the actual Generic Repository type.
  • Looking at your question, it's clear you just want to reduce repetitiveness in your codebase (DRY is important, after all) - but be careful about being too aggressive in implementing DRY because you may have different queries that look the same or similar but the differences might matter - so think carefully about what you're eliminating and that you won't be creating extra work for yourself or others in future.

  • In my experience and in my professional opinion: the "best" way to centrally define common queries is by defining Extension Methods for your DbContext and/or DbSet<T> types that build (but do not materialize!) an IQueryable<T>.

    • Define generic extension-methods on DbSet<T> constrained with an interface if they apply to different entity types.
    • Define non-generic extension-methods on DbSet<EntityTypeName> if they're specific to a single entity type.
    • Define non-generic extension-methods on DbContext if they're queries for multiple entity types (e.g. a JOIN query).
      • These still could be constrained generic extension-methods too, but it's non-trivial to get a DbSet<T> object reference for any T.
  • Another advantage of only defining methods that return IQueryable<T> is that you can compose them - which is a huge benefit if you have (for example) a complicated .Where() condition that you want to re-use in another query without repeating yourself.

Here's an example of a set of constrained generic extension-methods that return IQueryable<T>:

interface IHasId
{
    Int32 Id { get; }
}

interface IHasName
{
    String Name { get; }
}

public static class QueryExtensions
{
    public static IQueryable<T> QueryById( this DbSet<T> dbSet, Int32 entityId )
        where T : IHasId
    {
        return dbSet.Where( e => e.Id == entityId  );
    }

    public static IQueryable<T> QueryByName( this DbSet<T> dbSet, String name )
        where T : IHasName
    {
        return dbSet.Where( e => e.Name == name );
    }
}

// You need to add the interfaces to your entity types, use partial classes for this if your entity types are auto-generated:

public partial class Person : IHasId, IHasName
{
}

Used like so:

MyDbContext db = ...

Person p = await db.People.QueryById( entityId: 123 ).SingleOrDefaultAsync();

List<Person> people = await db.People.QueryByName( name: "John Smith" ).ToListAsync();

And if you want to add materialized queries, then that's okay too (but you won't be able to compose them - which is why it's best to stick to only adding extensions that return IQueryable<T>:

public static class QueryExtensions
{
    public static Task<T> GetSingleAsync( this DbSet<T> dbSet, Int32 entityId )
        where T : IHasId
    {
        return dbSet.SingleOrDefaultAsync( e => e.Id == entityId );
    }

     public static Task<List<T>> GetAllByNameAsync( this DbSet<T> dbSet, String name )
        where T : IHasName
    {
        return dbSet.Where( e => e.Name == name ).ToListAsync();
    }
}

Used like so:

MyDbContext db = ...

Person p = await db.People.GetSingleAsync( entityId: 123 );

List<Person> people = await db.People.GetAllByNameAsync( name: "John Smith" );
Dai
  • 141,631
  • 28
  • 261
  • 374
  • I am actually using store procedure as i mentioned here in ```this.context.Query().FromSql("{0}, {1}",procName, id).FirstOrDefault();``` to run query in sql server. Here procName defines the StoreProcedure Name. Can you tell me the how can i handle that for query in database using store procedure. – Shakibuz_Zaman Jul 07 '20 at 06:44
  • @Shakibuz_Zaman Entity Framework can generate methods for stored-procedures by itself without you needing to add them yourself. Is there a reason you're not using EF's generated stored-procedure wrapper methods? – Dai Jul 07 '20 at 06:49
  • I thought generating queries for entity framwork core might be costly. If i generate the queries my own, it might be faster as the api don't need to generate the queries any more. – Shakibuz_Zaman Jul 07 '20 at 07:49
  • You shouldn't use Stored Procedures for read-only queries, that's what Table-Valued-Functions and Views as for (they can also be inlined by SQL Server). Only use stored-procedures for operations that mutate data. Also, what do you mean by "costly" (in terms of initialization cost? runtime performance? design-time cost?). I stress that EF has a great reputation for generating optimal SQL for queries expressed using Linq-to-Entities, and as I said earlier: the ability to recompose `IQueryable` is a tremendous advantage. – Dai Jul 07 '20 at 08:11
  • In my own projects - the only times I've needed to use a stored-procedure or write a query using TVF is when I'm writing reports queries that use features of T-SQL that *cannot* be expressed at all when using Linq (e.g. recursive CTE queries, window functions or multi-result queries) or when using Linq would be too tedious (e.g. selecting a subset of columns without using anonymous types). – Dai Jul 07 '20 at 08:14
  • Can you look [here](https://stackoverflow.com/questions/9739230/entity-framework-vs-stored-procedures-performance-measure)? EF is kind of almost 7 times slower than Store procedure. Actually first i was also interested to work with EF but than my brother told about the performance of EF and asked to continue with store procedure. – Shakibuz_Zaman Jul 07 '20 at 09:21
  • @Shakibuz_Zaman EF itself doesn't have any significant performance issues besides its initialization time (which is a one-time cost when your program starts). That question you linked to is from 2012 - and EF has changed a lot since then (as we're up to EF6 and EF Core 3 now) so much of the early criticism of EF really doesn't apply today. I also feel most of the answers advocating the use of stored-procedures that don't mention TVFs were probably sound at the time, but TVFs are amazing and EF supports using them directly. – Dai Jul 07 '20 at 09:43
  • Ok, Thank you @Dai for these informations. I will try to convince my co-workers to work with EF. But still i have curiosity of know the solution of my posted question. May be we were a bit out of context for a moment thought i am grateful to you for these informations. Is there any solution of my question? – Shakibuz_Zaman Jul 07 '20 at 09:51
  • @Shakibuz_Zaman "Is there any solution of my question?" - to what extent does my posted answer not answer your question? What gaps are left? – Dai Jul 07 '20 at 11:29
  • I am talking about the store procedure solution actually. You explained the EF implementation in your answer, if i am not wrong. – Shakibuz_Zaman Jul 07 '20 at 12:00