Yes, hard coding SQL strings into application code generally is an anti-pattern.
Let's try to set aside the tolerance we have developed from years of seeing this in production code. Mixing completely different languages with different syntax in the same file is generally not a desirable development technique. This is different than template languages like Razor which are designed to give contextual meaning to multiple languages. As Sava B. mentions in a comment below, SQL in your C# or other application language (Python, C++, etc.) is a string like any other and is semantically meaningless.
The same applies when mixing more than one language in most cases, though there are obviously situations where doing so is acceptable, such as inline assembly in C, small and comprehensible snippets of CSS in HTML (noting that CSS is designed to be mixed with HTML), and others.
(Robert C. Martin on mixing languages, Clean Code, Chapter 17, "Code Smells and Heuristics", page 288)
For this response, I will focus SQL (as asked in the question). The following issues may occur when storing SQL as an à la carte set of disassociated strings:
- Database logic is difficult to locate. What do you search for to find all your SQL statements? Strings with "SELECT", "UPDATE", "MERGE", etc.?
- Refactoring uses of the same or similar SQL becomes difficult.
- Adding support for other databases is difficult. How would one accomplish this? Add if..then statements for each database and store all queries as strings in the method?
- Developers read a statement in another language and become distracted by the shift in focus from the method's purpose to the method's implementation details (how and from where data is retrieved).
- While one-liners may not be too much of a problem, inline SQL strings start to fall apart as statements become more complex. What do you do with a 113 line statement? Put all 113 lines in your method?
- How does the developer efficiently move queries back and forth between their SQL editor (SSMS, SQL Developer, etc.) and their source code? C#'s
@
prefix makes this easier, but I have seen a lot of code that quotes each SQL line and escapes the newlines.
"SELECT col1, col2...colN"\
"FROM painfulExample"\
"WHERE maintainability IS NULL"\
"AND modification.effort > @necessary"\
- Indentation characters used to align the SQL with surrounding application code are transmitted over the network with each execution. This is probably insignificant for small scale applications, but it can add up as the software's usage grows.
Full ORMs (Object-Relational mappers like Entity Framework or Hibernate) can eliminate randomly peppered SQL in application code. My use of SQL and resource files is but an example. ORMs, helper classes, etc. can all help accomplish the goal of cleaner code.
As Kevin said in an earlier answer, SQL in code can be acceptable in small projects, but large projects start out as small projects, and the probability most teams will go back and do it right is often inversely proportional to the code size.
There are many simple ways to keep SQL in a project. One of the methods that I often use is to put each SQL statement into a Visual Studio resource file, usually named "sql". A text file, JSON document, or other data source may be reasonable depending on your tools. In some cases, a separate class dedicated to ensconcing SQL strings may be the best option, but could have some of the issues described above.
SQL Example: Which looks more elegant?:
using(DbConnection connection = Database.SystemConnection()) {
var eyesoreSql = @"
SELECT
Viewable.ViewId,
Viewable.HelpText,
PageSize.Width,
PageSize.Height,
Layout.CSSClass,
PaginationType.GroupingText
FROM Viewable
LEFT JOIN PageSize
ON PageSize.Id = Viewable.PageSizeId
LEFT JOIN Layout
ON Layout.Id = Viewable.LayoutId
LEFT JOIN Theme
ON Theme.Id = Viewable.ThemeId
LEFT JOIN PaginationType
ON PaginationType.Id = Viewable.PaginationTypeId
LEFT JOIN PaginationMenu
ON PaginationMenu.Id = Viewable.PaginationMenuId
WHERE Viewable.Id = @Id
";
var results = connection.Query<int>(eyesoreSql, new { Id });
}
Becomes
using(DbConnection connection = Database.SystemConnection()) {
var results = connection.Query<int>(sql.GetViewable, new { Id });
}
The SQL is always in an easy-to-locate file or grouped set of files, each with a descriptive name that describes what it does rather than how it does it, each with space for a comment that will not interrupt the flow of application code:
This simple method executes a solitary query. In my experience, the benefit scales as use of the "foreign language" grows more sophisticated.
My use of a resource file is just an example. Different methods may be more appropriate depending on the language (SQL in this case) and platform.
This and other methods resolve the list above in the following manner:
- Database code is easy to locate because it is already centralized. In larger projects, group like-SQL into separate files, perhaps under a folder named
SQL
.
- Support for a second, third, etc. databases is easier. Make an interface (or other language abstraction) that returns each database's unique statements. The implementation for each database becomes little more than statements similar to:
return SqlResource.DoTheThing;
True, these implementations can skip the resource and contain the SQL in a string, but some (not all) problems above would still surface.
- Refactoring is simple -- just reuse the same resource. You can even use the same resource entry for different DBMS systems much of the time with a few format statements. I do this often.
- Use of the secondary language can use descriptive names e.g.
sql.GetOrdersForAccount
rather than more obtuse SELECT ... FROM ... WHERE...
- SQL statements are summoned with one line regardless of their size and complexity.
- SQL can be copied and pasted between database tools like SSMS and SQL Developer without modification or careful copying. No quotation marks. No trailing backslashes. In the case of the Visual Studio resource editor specifically, one click highlights the SQL statement. CTRL+C and then paste it into the SQL editor.
Creation of SQL in a resource is quick, so there is little impetus to mix resource usage with SQL-in-code.
Regardless of chosen method, I have found that mixing languages usually reduces code quality. I hope that some issues and solutions described here help developers eliminate this code smell when appropriate.