1

I was given a few dozen legacy SQL statements that are each hundred(s) of lines long. Each SQL is mapped to code with its own unique POCO in a shared Models project.

For example, the SQL Select Name, Birthday From People has an equivilent POCO in the Models project:

public class BirthdayPerson : SqlResultBase {
    public string Name { get; set; }
    public datetime Birthday { get; set; }

    //SqlResultBase abstraction:
    public string HardcodedSql { get {
        return "Select Name, Birthday From People";
    }}
}

In my DAL, I have a single generic SQL runner whose <T> represents the POCO for the SQL. So my business logic can call GetSqlResult<BirthdayPerson>():

public IEnumerable<T> GetSqlResult<T>() where T : SqlResultBase, new() {
    return context.Database.SqlQuery<T>((new T()).HardcodedSql);
}

The problem is that my Models library is used across the application, and I don't want SQL exposed across the application in that HardcodedSql property.

This is the architecture I'm using:

Suamere's Domain

Suamere
  • 5,691
  • 2
  • 44
  • 58
  • would it not perhaps help if you rather create a function in the class and return the result from the function, that way you could place the raw sql in a private variable and not expose it everywhere? – Louis Lewis Nov 10 '14 at 16:37
  • That almost sounds great. As far as I understand what you're suggesting, I'm pretty sure that the frontend could still just call the method and read the exposed SQL. – Suamere Nov 10 '14 at 16:46
  • you would be correct with that assumption :) – Louis Lewis Nov 10 '14 at 16:46
  • Sorry I misunderstood your last comment, the function would not return the SQL, it would rather return the result of the query, as in IEnumerable of the type. The UI could still call this method and have the same result returned – Louis Lewis Nov 10 '14 at 17:09

3 Answers3

1

The simplest solution would be to make HardcodedSql internal instead of public so it's only visible within a DAL Project. If the DAL is a separate project from the model you could use InternalsVisibleTo to expose it to that project. This assumes you can configure your project structure accordingly.

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • I obviously didn't read this correctly at first. I'm going to look into InternalsVisibleTo real quick and get back to this, it sounds great. – Suamere Nov 10 '14 at 16:50
  • Ah yes, I remember that now. I think I discarded ever knowing about InternalsVisibleTo because, in my mind, it would be signifying a code smell. In the case of my current situation, there certainly IS a code smell, and I don't like it. So while InternalsVisibleTo would help, I'm super-hoping for a solution to get rid of the stink. It's also possible that's unavoidable for the requirements I've been given, but hopefully that's not the case and I'm just overlooking something. A SOLID re-factor may be in order here, and suggestions down that path would be the perfect answer. – Suamere Nov 10 '14 at 16:57
  • So what is your project structure? Do you have separate projects already for Model, DAL, Business Layer, ...? – Ian Mercer Nov 10 '14 at 17:22
  • Added my project structure to the original post. – Suamere Nov 10 '14 at 21:29
  • You should, separately, also look at `Automapper` as a way to have separate POCOs for your DataLayer and your other layers. Often the objects used for persistence need to be able to evolve separately from the objects used in the UI. – Ian Mercer Nov 10 '14 at 21:35
1

I suggest perhaps two possible ways of dealing with the question.

As for the first method, I would rather change how the sql is accessed and wrap the call locally in a method. So the class may have a function called public IEnumerable GetFromSql() you could pass in a context, or create a new one, I am not sure how you have setup EF in your project. this way you never publically expose the raw sql, as you would rather make it a private variable or local constant perhaps and simply access it from within the function.

As a second option, and I have actually done this and it turned out pretty great, was I moved all the sql to views and used EF to access them. That way there was no sql pollution in my code. Seeing that the models already exists, the result from calling the views would match the types that you already have.

Louis Lewis
  • 1,298
  • 10
  • 25
  • Using Views would be a great thing, but the company gave me the SQL to place in code. It's a great suggestion, but they refuse to use Views, Stored Procs, or anything except Triggers in their DB. Thank you for that suggestion, though. – Suamere Nov 10 '14 at 21:27
1

At first you have to separate your model (i.e. POCOs) from the SQL which actually belongs to the DAL. Inversion of Control is right way to do this. Instead of generic sql runner it is better to register mappings in the IoC container from abstract repositores (e.g. IRepository<MyPOCO>) to implementations that contain the SQLs.

EDIT: To be more concrete, a possible solution:

  • Place all SQLs to a separate file(s) inside DAL, for example to a set of embedded resource files with name convention, e.g. Legacy-{0}.sql where {0} is name of the POCO.
  • Create a generic implementation of legacy repository that uses POCO name as a key and picks corresponding Legacy-{0}.sql file from the resource set. Note that there may be other implementations as well that use other data access techniques, like ORM.
  • In the composition root register explicitly all mappings from the legacy POCOs to the legacy implementation: IRepository<MyPOCO1> => LegacyRepo<MyPOCO1>; IRepository<MyPOCO2> => LegacyRepo<MyPOCO2>; etc. Moreover you may register other mappings from non-legacy entities to other implementations of repository.
neleus
  • 2,230
  • 21
  • 36
  • In addition, you may move them to SQL views, as Louis Lewis suggests. Then the repository implementation would reference a view rather than contain raw SQL statement – neleus Nov 10 '14 at 21:05
  • You're right that the SQL doesn't belong on the POCO and should instead be in the DAL. That's the main point of the question. Currently, my Composition Root is either in a local app_start, or in the service app_start, and the composition is using constructor-injection to provide dependency injection at all levels. What could be a good answer is some elaboration on the part where you say "to implementations that contain the SQLs". Do you mean to get rid of the generic and make a duplicate function for every type, other than the SQL expression and the `Type` (the two variables of the generic)? – Suamere Nov 10 '14 at 21:21
  • No, making many overloaded (or duplicate) methods breaks Open-Closed principle. You need only one generic implementation similar to your generic sql runner and inject concrete SQL into each concrete mapping: `IRepository => (RepoImpl + SQL that corresponds to POCO1); IRepository => (RepoImpl + SQL that corresponds to POCO2); etc` – neleus Nov 10 '14 at 21:47
  • You mentioned that you use constructor injection. In this case RepoImpl constructor will have one additional parameter for the SQL. I cannot give more detailed example because exact mapping code depends on IoC that you use. – neleus Nov 10 '14 at 21:53
  • I know what you're saying. In this case, where does one of these 400 line SQL statements live? 8000 lines in the composition root? – Suamere Nov 10 '14 at 21:57
  • Again no, better is to place it a) to a database view or b) to separate config file, for example and reference them by a key. Of course there may be other solutions. – neleus Nov 10 '14 at 22:00
  • OOoOoooOOO... A config file. I may be able to spin that without many more bells and whistles. I already have a dependency injected for config reader. If I pass in a "key" of the POCO Type, I could pull back a SQL. Maybe elaborate on that in your answer. – Suamere Nov 10 '14 at 22:25
  • I've added details to my origianl post. – neleus Nov 11 '14 at 08:09