Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I have some code that allows for SQL reuse for C#. I'm pretty happy with it, but the fact that I can't find anything similar on the web makes me think I'm going about it the wrong way. I have a GitHub repo with a Visual Studio solution, and the code is repeated down below.

Here's the use case:

I'm working on a fairly standard C# and SQL Server project where we're generating reports. Each report is essentially the results of SQL query, displayed in HTML in the browser. I'd rather not use an ORM like Entity Framework, since we need to exactly control the SQL for performance reasons (e.g. use SQL server windowing functions, table hints, etc).

However, many of the reports are similar, just slicing and dicing by different attributes, so there's a desire for code reuse. SQL Server has stored procedures, views, and functions, but the consensus seems to be that you'll get into trouble trying to use them to create abstractions. To get around this I'm trying to use C# to reuse SQL code.

I'm essentially using Visual Studio's T4 text templates to generate SQL files at development time. I'm then passing the SQL to Stack Exchange's Dapper.NET project to handle variable substitution, issuing the query, and POCO [de]serialization.

Given a query like this, in the template file CuteAnimalsByLocation.tt:

<#@ output extension=".sql" #>
select * from animals a
where a.IsCute = 1 and a.IsFuzzy = 1 and a.Location = @location

And this file, DeadlyMachinesByLocation.tt:

<#@ output extension=".sql" #>
select * from DeadlyMachines m
where m.IsLethal = 1 and m.HasExplosives = 1 and m.Location = @location

I can reuse the above queries in a more complex query by writing the following, AnimalsInPeril.tt:

<#@ output extension=".sql" #>

WITH 
CuteAnimalsInLocation as 
( 
    <#@ include file="CuteAnimalsByLocation.tt" #>
),
DeadlyMachinesInLocation as
(
    <#@ include file="DeadlyMachinesByLocation.tt" #>
)
select a.* from CuteAnimalsInLocation a
inner join DeadlyMachinesInLocation m on a.Location = m.Location

I can run all 3 of these queries like this: (assuming the POCOs Animal and DeadlyMachine exist; they're just POCOs that map to the table schemas):

using (var connection = new SqlConnection(ConnectionString))
{
    connection.Open();
    var queryParams = new { Location = "NorthAmerica" };

    IEnumerable<Animal> animalsNeedingHelp = 
        connection.QueryFromFile<Animal>("AnimalsInPeril", queryParams);
    IEnumerable<Animal> cuteAnimals = 
        connection.QueryFromFile<Animal>("CuteAnimalsByLocation", queryParams);
    IEnumerable<DeadlyMachine> deadlyMachines = 
        connection.QueryFromFile<DeadlyMachine>("DeadlyMachinesByLocation", queryParams);
}

connection.QueryFromFile is defined as this, and depends on Dapper:

public static class DapperFileExtensions
{
    public static IEnumerable<TReturn> QueryFromFile<TReturn>(this IDbConnection cnn, string file, object params = null)
    {
        var sql = File.ReadAllText(file + ".sql");
        return cnn.Query<TReturn>(sql, params);
    }
}

Can anyone see problems with this approach? I guess the only thing I can think of is that I could add caching around the File.ReadAllText call.

share|improve this question
1  
Hi Will, I wonder if you're overthinking it a little to much and may just end up with unmaintainable code from a C# and an SQL perspective; if these are reports you'll probably have to endless tweak them at the whim of the users, too much reuse will prevent tweaking; having views and/or stored procedures in a database would help and make it easy to change. Going to a low level of generic abstraction may mean your code won't make any sense 6 months down the line. –  Phill Sep 2 at 16:29
    
Thanks @Phill, that's exactly the sort of perspective I'm looking for. If I keep this level of abstraction at the same level of abstraction of views and sprocs, can you think of downsides? I see the benefits of version control, simplified deployment, and easing the job of the RDBMS query optimizer. –  Will Sep 3 at 2:45
    
My team is using an old port of MyBatis to so something similar, with SQL query snippets defined in XML files. It's odd, but with a few tools written a long time ago to pre-process the XML files it's working great for us. I'd like to rewrite the tools in golang or nodejs instead of C# Console + WinForms, but other than that no complaints. –  yzorg 23 hours ago

1 Answer 1

The main problem I see with this approach is you have magical strings, which are not compile time safe.

I highly recommend you look into using an ORM like entity framework

public class AnimalsRepository : IDisposable
{
    private MyDataModel Model { get; set; }
    public AnimalsRepository()
    {
        Model = new MyDataModel();
    }
    public void Dispose()
    {
        if (this.Model != null)
            this.Model.Dispose();
    }
    public IQueryable<Animal> GetCuteAnimals(string location)
    {
        return this.Model.Animals.Where(a => a.IsCute && a.IsFuzzy && a.Location == location);
    }
    public IQueryable<DeadlyMachine> GetDeadlyMachines(string location)
    {
        return this.Model.DeadlyMachines.Where(m => m.IsLethal && m.HasExplosives && m.Location == location);
    }
    public IQueryable<Animal> GetAnimalsInPeril(string location)
    {
        return from a in this.GetCuteAnimals(location)
               join m in this.GetDeadlyMachines(location) on a.Location equals m.Location
               select a;

    }
}

See Nuget Package: https://www.nuget.org/packages/EntityFramework

share|improve this answer
2  
From the question: "I'd rather not use an ORM like Entity Framework, since we need to exactly control the SQL for performance reasons (e.g. use SQL server windowing functions, table hints, etc)." –  BCdotWEB Sep 2 at 8:30
    
I love the composability and static typing of IQueryable in EF. I'm using it in my application code, but for report-style SQL it doesn't give me enough control (as @BCdotWEB pointed out). But yeah, I'm basically trying to recreate the composability that I lose when moving from EF to raw SQL. –  Will Sep 2 at 9:40

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.