在源代码中编写SQL是否被视为反模式?


87

将SQL硬编码到这样的应用程序中是否被认为是一种反模式:

public List<int> getPersonIDs()
{    
    List<int> listPersonIDs = new List<int>();
    using (SqlConnection connection = new SqlConnection(
        ConfigurationManager.ConnectionStrings["Connection"].ConnectionString))
    using (SqlCommand command = new SqlCommand())
    {
        command.CommandText = "select id from Person";
        command.Connection = connection;
        connection.Open();
        SqlDataReader datareader = command.ExecuteReader();
        while (datareader.Read())
        {
            listPersonIDs.Add(Convert.ToInt32(datareader["ID"]));
        }
    }
    return listPersonIDs;
}

我通常会有一个存储库层等,但是为了简单起见,我在上面的代码中将其排除在外。

我最近收到了一位同事的一些反馈,该同事抱怨SQL是用源代码编写的。我没有机会问为什么,他现在已经离开了两个星期(也许还有更多)。我认为他的意思是:

  1. 使用LINQ
  2. 对SQL使用存储过程

我对么?在源代码中编写SQL是否被视为反模式?我们是一个从事此项目的小组。我认为存储过程的好处是SQL Developers可以参与开发过程(编写存储过程等)。

编辑 以下链接讨论了硬编码的SQL语句:https : //docs.microsoft.com/zh-cn/sql/odbc/reference/develop-app/hard-coded-sql-statements。准备SQL语句有什么好处?


31
“使用Linq”和“使用存储过程”不是原因;它们只是建议。等待两个星期,然后问他原因。
罗伯特·哈维

47
堆栈交换网络使用称为Dapper的微型ORM。我认为可以说绝大多数Dapper代码是“硬编码SQL”(或多或少)。因此,如果这是一种不良做法,那么这是地球上最著名的Web应用程序之一所采用的不良做法。
罗伯特·哈维

16
为了回答有关存储库的问题,无论您放置在哪里,硬编码的SQL仍然是硬编码的SQL。区别在于,存储库为您提供了封装硬编码SQL的位置。它是一个抽象层,从程序的其余部分隐藏了SQL的详细信息。
罗伯特·哈维

26
不,代码中的SQL不是反模式。但这对于一个简单的SQL查询来说是很多样板代码。
GrandmasterB

45
“在源代码中”和“遍及源代码的全部”之间是有区别的
tofro

Answers:


112

为简单起见,您排除了关键部分。存储库是持久性的抽象层。我们将持久性分离到其自己的层中,以便我们可以在需要时更轻松地更改持久性技术。因此,在持久层之外使用SQL完全可以避免使用单独的持久层的麻烦。

结果是:在特定于SQL技术的持久层内,SQL很好(例如,SQL在a中很好,SQLCustomerRepository但在a中不是MongoCustomerRepository)。在持久层之外,SQL破坏了您的抽象,因此被我认为是非常不好的做法

至于LINQ或JPQL之类的工具:那些工具只能在那里提取SQL的风格。在存储库之外使用LINQ-Code或JPQL查询会像原始SQL一样破坏持久性抽象。


单独的持久层的另一个巨大优势是,它使您无需设置数据库服务器即可对业务逻辑代码进行单元测试。

您可以获得低内存配置文件,快速的单元测试,并且在您的语言支持的所有平台上都具有可重复的结果。

在MVC + Service体系结构中,这是模拟存储库实例,在内存中创建一些模拟数据并定义存储库应在调用某个getter时返回该模拟数据的简单任务。然后,您可以定义每个单元测试的测试数据,而不必担心以后清理数据库。

测试写入数据库的过程非常简单:验证持久层上的相关更新方法已被调用,并断言在发生这种情况时实体处于正确的状态。


80
在绝大多数现实世界项目中,“我们可以改变持久性技术”的想法是不现实和不必要的。SQL /关系数据库与NoSQL数据库(例如MongoDB)完全不同。请参阅此详细文章。最多,一个开始使用NoSQL的项目最终将意识到他们的错误,然后切换到RDBMS。以我的经验,允许从业务代码中直接使用面向对象的查询语言(例如JPA-QL)可提供最佳结果。
罗杰里奥

6
如果更改持久层的机会很小,该怎么办?如果在更改持久层时没有一对一的映射该怎么办?额外的抽象级别有什么优势?
pllee

19
@Rogério正如您所说,从NoSQL存储迁移到RDBMS,反之亦然,这是不切实际的(假设技术选择是合适的)。但是,我参与了许多实际项目,我们从一个RDBMS迁移到另一个RDBMS。在那种情况下,封装的持久层绝对是一个优势。
戴维(David)

6
“在绝大多数现实世界项目中,“我们可以改变持久性技术”的想法是不现实和不必要的。”我公司以前是按照这种假设编写代码的。我们不得不重构整个代码库,以不支持一个,而是支持三个完全不同的存储/查询系统,并且正在考虑使用第三个和第四个。更糟糕的是,即使我们只有一个数据库,缺乏整合也会导致各种代码错误。
NPSF3000 '17

3
@Rogério您知道“现实世界中的绝大多数项目”吗?在我的公司中,大多数应用程序都可以使用许多常见的DBMS之一,这非常有用,因为大多数我们的客户确实对此有非功能性要求。
BartoszKP's

55

当今,大多数标准业务应用程序使用具有不同职责的不同层。但是,使用图层您的应用程序,以及层具有责任是由你和你的团队。在决定将SQL直接放置在您已经显示给我们的函数中是否正确之前,您需要了解

  • 您的应用程序中的哪一层负责

  • 上面的功能来自哪一层

没有“一刀切”的解决方案。在某些应用程序中,设计人员更喜欢使用ORM框架,并让该框架生成所有SQL。在某些应用程序中,设计人员倾向于将此类SQL仅存储在存储过程中。对于某些应用程序,存在SQL的地方是手写的持久性(或存储库)层,对于其他应用程序,可以通过将SQL严格放置在该持久性层中来在某些情况下定义异常。

因此,您需要考虑的是:特定应用程序中需要需要哪些层,以及希望如何承担责任?您写了“我通常会有一个存储库层”,但是您要在该层中承担什么确切的职责,以及您要把什么职责放到其他地方?首先回答,然后您可以自己回答问题。


1
我已经编辑了问题。不确定是否可以照亮。
w0051977

18
@ w0051977:您发布的链接没有告诉我们有关您的申请的任何信息,对吗?似乎您正在寻找一条简单的,适用于所有应用程序的,不宜放置SQL的规则。空无一人。这是必须针对您的单个应用程序(可能与您的团队一起)做出的设计决策。
布朗

7
+1。特别要指出的是,就某种方式的“硬编码”或可重用性,可维护性等方面而言,方法中的SQL与存储过程中的SQL并没有真正的区别。程序可以用一种方法来完成。当然,过程可能是有用的,但死记硬背的规则“我们不能在方法中使用SQL,因此让我们将其全部放入过程中”可能只会产生很多麻烦,却几乎没有好处。

1
@ user82096我想说,一般而言,将db访问代码放入SP几乎对我所见过的每个项目都是有害的。(MS堆栈)除了实际的性能下降(严格的执行计划缓存)外,通过分散逻辑来使系统维护更加困难,并且由与应用程序逻辑开发人员不同的人员来维护SP。尤其是在Azure上,部署槽之间的代码更改就像工作的几秒钟一样,但是在槽之间进行非代码优先/数据库优先的架构迁移却有点麻烦。
Sentinel

33

Marstato给出了很好的答案,但我想补充一点评论。

源代码中的SQL不是反模式,但是会引起问题。我记得以前曾经需要将SQL查询放入放置在每种表单中的组件的属性中。这使事情变得非常丑陋,而且非常麻烦,您必须跳过障碍才能找到查询。我坚决主张尽可能在我使用的语言范围内集中数据库访问 您的同事可能会回想起这些黑暗的日子。

现在,有些评论谈论供应商锁定,因为这自动是一件坏事。不是。如果我每年要签署一张六位数的支票以使用Oracle,那么您可以打赌我希望访问该数据库的任何应用程序都适当地使用额外的Oracle语法但要充分发挥作用 如果有一种“甲骨文方式”编写不会破坏数据库的SQL,而我的闪亮数据库却被代码编写者严重破坏了普通ANSI SQL的代码破坏了,我将不满意。是的,更改数据库将更加困难,但是我只看到它在大型客户站点上进行了20多年,却有几次是从DB2-> Oracle迁移而来的,因为托管DB2的大型机已经过时并且已经退役。 。是的,这是供应商锁定,但是对于公司客户而言,实际上需要购买价格昂贵的功能强大的RDBMS(例如Oracle或Microsoft SQL Server),然后充分利用它。您有一份支持协议作为您的舒适毯。如果我要为具有丰富存储过程实现的数据库付费,

这引出了下一点,如果您正在编写访问SQL数据库的应用程序,则必须学习SQL以及其他语言,通过学习,我的意思是查询优化。如果您编写的SQL生成代码会用几乎相同的查询冲刷SQL缓存,那么我会很生气。

没有任何借口,没有躲在冬眠的墙后面。错误使用的ORM 确实会削弱应用程序的性能。我记得几年前看到过关于堆栈溢出的问题,具体如下:

在Hibernate中,我要遍历250,000条记录,以检查几个属性的值并更新符合特定条件的对象。它的运行速度有点慢,该如何加快速度?

怎么样“更新表SET field1 =其中field2为True且Field3> 100”。创建和处理250,000个对象很可能是您的问题...

即当不适合使用它时,请忽略休眠。了解数据库。

因此,总而言之,将SQL嵌入代码中可能不是一个好习惯,但是您最终会尝试避免嵌入SQL,这会导致更糟糕的事情。


5
我没有说“供应商锁定”是坏事。我说过,这需要权衡。在具有xaaS的db的当今市场中,用于运行自托管db的旧合同和许可证将越来越少。与10年前相比,如今将数据库从供应商A更改为B相当容易。如果您阅读了评论,则不赞成利用数据存储的任何功能。因此,很容易将一些特定的供应商详细信息放到某些与供应商无关的东西(应用程序)中。我们努力遵循SOLiD,直到最后将代码锁定到单个数据存储中。没什么大不了
Laiv

2
这是一个很好的答案。如果编写了可能的最差的代码,通常正确的方法是导致最不严重的情况的方法。最糟糕的ORM代码可能比最糟糕的嵌入式SQL更糟糕。
jwg

@Laiv的优点PaaS有点改变游戏规则-我将不得不更多地考虑其含义。
mcottle '17

3
@jwg我倾向于说高于平均水平的ORM代码比低于低于平均水平的嵌入式SQL :)
mcottle

@macottle假设ppl编写的嵌入式SQL的平均水平低于编写ORM的平均水平。我非常怀疑。那里的许多开发人员没有能力先写左JOINs,然后先检查SO。
Laiv

14

是的,将SQL字符串硬编码为应用程序代码通常是一种反模式。

让我们试着抛开我们多年来在生产代码中看到这一点而开发的公差。在同一文件中混合使用不同语法的完全不同的语言通常不是理想的开发技术。这与Razor等模板语言不同,后者旨在为多种语言提供上下文含义。正如Sava B.在下面的评论中提到的那样,您的C#或其他应用程序语言(Python,C ++等)中的SQL是与其他字符串一样的字符串,在语义上没有意义。在大多数情况下,混合一种以上的语言时也是如此,尽管在某些情况下显然可以接受,例如C中的内联汇编,HTML中的CSS片段小而易于理解(请注意CSS旨在与HTML混合) ), 和别的。

罗伯特·C·马丁(Robert C.  288 (Robert C. Martin关于混合语言,“ 干净代码”,第17章,“代码气味和启发式方法”,第288页)

对于此响应,我将重点关注SQL(如问题中所述)。将SQL存储为分离后的字符串的点菜集时,可能会出现以下问题:

  • 数据库逻辑很难定位。您要搜索什么以查找所有SQL语句?带“ SELECT”,“ UPDATE”,“ MERGE”等的字符串?
  • 重构相同或相似SQL的用法变得困难。
  • 添加对其他数据库的支持很困难。一个人如何做到这一点?为每个数据库添加if..then语句,并将所有查询作为字符串存储在方法中?
  • 开发人员阅读另一种语言的声明,并因注意力从方法目的转移到方法的实现细节(如何以及从何处检索数据)而分心。
  • 虽然单行可能不是太大的问题,但是随着语句变得更加复杂,内联SQL字符串开始崩溃。您用113行语句做什么?将所有113行放入您的方法中?
  • 开发人员如何有效地在SQL编辑器(SSMS,SQL Developer等)和源代码之间来回移动查询?C#的@前缀使此操作变得更容易,但是我看到了很多引用每个SQL行并转义换行符的代码。 "SELECT col1, col2...colN"\ "FROM painfulExample"\ "WHERE maintainability IS NULL"\ "AND modification.effort > @necessary"\
  • 每次执行时,都会通过网络传输用于使SQL与周围的应用程序代码对齐的缩进字符。对于小型应用程序来说,这可能无关紧要,但是随着软件使用量的增加,它可能会加起来。

完整的ORM(对象关系映射器,例如Entity Framework或Hibernate)可以消除应用程序代码中的随机添加的SQL。我对SQL和资源文件的使用只是一个例子。ORM,帮助程序类等都可以帮助实现更干净的代码的目标。

正如Kevin在较早的答案中所说的那样,代码中的SQL在小型项目中是可以接受的,但是大型项目从小型项目开始就可以了,大多数团队回头做正确的可能性通常与代码大小成反比。

有很多简单的方法可以将SQL保留在项目中。我经常使用的方法之一是将每个SQL语句放入通常称为“ sql”的Visual Studio资源文件中。根据您的工具,文本文件,JSON文档或其他数据源可能是合理的。在某些情况下,最好使用一个单独的类来维护SQL字符串,但这可能是上述一些问题。

SQL示例:哪个看起来更优雅?:

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 });
}

成为

using(DbConnection connection = Database.SystemConnection()) {
    var results = connection.Query<int>(sql.GetViewable, new { Id });
}

SQL始终位于易于查找的文件或文件集中,每个文件都有一个描述性名称,以描述其功能而不是其操作方式,每个名称中都留有注释空间,不会引起应用程序流程中断:

资源中的SQL

这个简单的方法执行一个单独的查询。以我的经验,随着“外语”的使用变得越来越复杂,收益也随之扩大。 我对资源文件的使用只是一个例子。取决于语言(在这种情况下为SQL)和平台,不同的方法可能更合适。

此方法和其他方法以下列方式解决上面的列表:

  1. 数据库代码很容易找到,因为它已经集中了。在较大的项目中,可以将like-SQL分组为单独的文件,也许在名为的文件夹下SQL
  2. 对第二,第三等数据库的支持更加容易。创建一个接口(或其他语言抽象)以返回每个数据库的唯一语句。每个数据库的实现只不过是类似于以下语句的语句:return SqlResource.DoTheThing;的确,这些实现可以跳过资源并将SQL包含在字符串中,但是上面的一些(并非全部)问题仍然存在。
  3. 重构很简单-只需重用相同的资源即可。您甚至可以通过几个格式语句在大部分时间将相同的资源条目用于不同的DBMS系统。我经常这样做。
  4. 第二语言的使用可以使用描述性名称,例如,sql.GetOrdersForAccount而不是更晦涩SELECT ... FROM ... WHERE...
  5. 不论其大小和复杂度如何,SQL语句都用一行来调用。
  6. 无需修改或仔细复制即可在SSMS和SQL Developer等数据库工具之间复制和粘贴SQL。没有引号。没有结尾的反斜杠。特别是在Visual Studio资源编辑器的情况下,单击一下即可突出显示SQL语句。CTRL + C,然后将其粘贴到SQL编辑器中。

在资源中创建SQL的速度很快,因此几乎没有动力将资源使用情况与代码中的SQL混合使用。

无论选择哪种方法,我都发现混合语言通常会降低代码质量。我希望这里描述的一些问题和解决方案可以帮助开发人员在适当的时候消除这种代码异味。


13
我认为这过于关注在源代码中使用SQL的不良批评。在同一文件中使用两种不同的语言不一定很糟糕。它取决于很多事情,例如它们如何关联以及如何呈现。
jwg

9
“在同一文件中使用不同的语法混合完全不同的语言”这是一个可怕的论点。它也将适用于Razor视图引擎以及HTML,CSS和Javascript。爱你的图画小说!
伊恩·纽森

4
这只是将复杂性推到了其他地方。是的,您的原始代码更简洁,但以添加开发人员现在出于维护目的而必须导航至的间接访问为代价。您这样做的真正原因是,如果在代码中的多个位置使用了每个SQL语句。这样,它实际上与任何其他普通重构(“提取方法”)没有什么不同。
罗伯特·哈维

3
更清楚地说,这不是对“我如何处理我的SQL字符串”问题的答案,而是对以下问题的回答:“我如何处理任何足够复杂的字符串集合”您的答案雄辩地讲了。
罗伯特·哈维

2
@RobertHarvey:我肯定我们的经验会有所不同,但是就我个人而言,我发现有关的抽象在大多数情况下是一个胜利。多年来,我肯定会完善我的抽象权衡,也许有一天有可能将SQL重新添加到代码中。YMMV。:)我认为微服务可能很棒,它们通常也会将您的SQL详细信息(如果完全使用SQL)与核心应用程序代码分开。我不是在提倡“使用我的特定方法:资源”,而是在提倡“通常不要混合使用石油和水的语言”。
查尔斯·伯恩斯

13

这取决于。可以使用多种不同的方法:

  1. 模块化SQL,并将其隔离为一组单独的类,函数或您的范式使用的任何抽象单元,然后使用应用程序逻辑对其进行调用。
  2. 将所有复杂的SQL移到视图中,然后仅在应用程序逻辑中执行非常简单的SQL,这样就无需对任何模块进行模块化。
  3. 使用对象关系映射库。
  4. YAGNI,只需直接在应用程序逻辑中编写SQL。

通常,如果您的项目已经选择了其中一种技术,则应与项目的其余部分保持一致。

(1)和(3)在保持应用程序逻辑和数据库之间的独立性方面都比较擅长,如果您用其他供应商替换数据库,则应用程序将继续编译并通过基本的冒烟测试。但是,大多数供应商并不完全符合SQL标准,因此无论您使用哪种技术,用任何其他供应商替换任何供应商都可能需要进行广泛的测试和错误查找。我对此持怀疑态度,因为这与人们想像的一样重要。当您无法获得满足您需求的当前数据库时,更改数据库基本上是最后的选择。如果发生这种情况,您可能选择的数据库很差。

(1)和(3)之间的选择主要取决于您对ORM的满意程度。我认为它们被过度使用了。它们不能很好地表示关系数据模型,因为行不像对象具有身份那样具有身份。您可能会遇到围绕唯一约束和联接的痛点,并且根据ORM的功能,您可能难以表达一些更复杂的查询。另一方面,(1)可能需要比ORM多得多的代码。

(2)以我的经验很少见。问题在于许多商店禁止SWE直接修改数据库模式(因为“这是DBA的工作”)。这本身并不一定是一件坏事;架构更改具有破坏事物的巨大潜力,可能需要仔细推出。但是,为了使(2)起作用,SWE至少应该能够以最小的官僚机构或没有官僚机构引入新的视图并修改现有视图的后备查询。如果您的工作地点不是这种情况,那么(2)可能对您不起作用。

另一方面,如果可以使(2)起作用,则它比大多数其他解决方案要好得多,因为它在SQL中保留了关系逻辑而不是应用程序代码。与通用编程语言不同,SQL是专门为关系数据模型设计的,因此在表达复杂的数据查询和转换方面表现更好。更改数据库时,视图也可以与模式的其余部分一起移植,但是它们会使这种移动更加复杂。

对于读取,存储过程基本上只是(2)的一个糟糕版本。我不建议以这种方式使用它们,但是,如果数据库不支持可更新的视图,或者您需要执行比一次插入或更新一行更复杂的操作(例如,交易,读后写等)。您可以使用触发器将存储过程耦合到视图(即CREATE TRIGGER trigger_name INSTEAD OF INSERT ON view_name FOR EACH ROW EXECUTE PROCEDURE procedure_name;),但对于这实际上是否是一个好主意,意见分歧很大。支持者会告诉您,它使应用程序执行的SQL尽可能简单。批评者会告诉您,这是“不可思议”的水平,您应该直接从应用程序中直接执行该过程。我会说这是一个好主意,如果你的存储过程看起来或行为很像的INSERTUPDATE或者DELETE,和糟糕的想法,如果是别人做的事情。最终,您必须自己决定哪种样式更有意义。

(4)是非解。对于小型项目或仅偶尔与数据库交互的大型项目,这可能是值得的。但是对于具有大量SQL的项目来说,这不是一个好主意,因为您可能会随意地在应用程序中散布相同查询的重复或变体,这会干扰可读性和重构。


所写的2实质上是假设一个只读数据库。存储过程以前在修改查询中并不罕见。
jpmc26

@ jpmc26:我在括号中写过文章...您对改进此答案有具体建议吗?
凯文

嗯 很抱歉在完成答案之前发表评论,然后分心。但是可更新的视图使跟踪表的更新位置变得相当困难。无论采用何种机制,将更新限制为直接在表上进行,在理解代码逻辑方面确实具有其优势。如果将逻辑限制在数据库中,那么没有存储过程就无法实施。它们还有一个优点,即允许一次调用多个操作。底线:我不认为你应该对他们这么刻薄。
jpmc26

@ jpmc26:公平。
凯文

8

在源代码中编写SQL是否被视为反模式?

不必要。如果您在此处阅读了所有注释,则将在源代码中找到用于对SQL语句进行硬编码的有效参数。

问题出在放置语句的位置。如果将SQL语句放置在整个项目的任何地方,那么您可能会忽略一些我们通常会遵循的SOLID原则。

假设他的意思;要么:

1)使用LINQ

要么

2)将存储过程用于SQL

我们不能说他的意思。但是,我们可以猜测。例如,我想到的第一个是供应商锁定。硬编码SQL语句可能会使您将应用程序紧密耦合到数据库引擎。例如,使用供应商的不符合ANSI的特定功能。

这不一定是错误的,也不是坏的。我只是指出事实。

忽略SOLID原则和供应商锁定可能会导致您忽略的不利后果。因此,通常最好与团队一起坐下来,揭露您的疑问。

我认为存储过程的好处是,SQL Developers可以参与开发过程(编写存储过程等)

我认为这与存储过程的优点无关。而且,如果您的同事不喜欢硬编码的SQL,那么将业务转移到存储过程中也很可能不喜欢他。

编辑:以下链接讨论了硬编码的SQL语句:https//docs.microsoft.com/zh-cn/sql/odbc/reference/develop-app/hard-coded-sql-statements。准备SQL语句有什么好处?

是。该文章列举了准备好的陈述的优点。这是一种SQL模板。比字符串连接更安全。但是该帖子既不鼓励您采用这种方式,也不鼓励您确认自己是对的。它仅说明了我们如何以安全有效的方式使用硬编码的SQL。

总结一下,请尝试先询问您的同事。发送邮件,给他打电话,...无论他是否回答,与团队坐在一起,揭露您的疑虑。寻找最适合您需求的解决方案。不要根据您在那里读到的东西做出错误的假设。


1
谢谢。对于“对SQL语句进行硬编码可能会使您将应用程序紧密耦合到db引擎” +1。如果他指的是SQL Server而不是SQL,那我就徘徊了,即使用SQLConnection而不是dbConnection。SQLCommand而不是dbCommand和SQLDataReader而不是dbDataReader。
w0051977 '17

否。我指的是不符合ANSI的表达式的用法。例如:select GETDATE(), ....GETDATE()是SqlServer的date函数。在其他引擎中,该函数具有不同的名称,不同的表达式,...
Laiv

14
“供应商锁定” ...人员/企业实际上多久将其现有产品的数据库迁移到另一个RDBMS?即使在仅使用ORM的项目中,我也从未见过这种情况:通常,软件也需要从头开始重写,因为两者之间的需求有所变化。因此,对我来说,考虑这一点很像“过早的优化”。是的,这完全是主观的。
奥利维尔·格雷戈尔

1
我不在乎多久发生一次。我很在乎这可能会发生。在未开发的项目中,从数据库中提取DAL的实现成本几乎为0。可能的迁移并非如此。反对ORM的论点很弱。ORM不像15年前的Hibernate一样绿色。我所看到的是很多“默认”配置和相当糟糕的数据模型设计。就像许多其他工具一样,许多人都在做“入门”教程,而他们并不在乎幕后发生的事情。该工具不是问题。问题在于谁不知道如何以及何时使用它。
Laiv

另一方面,锁定供应商并不一定是不好的。如果被问到我会说我不喜欢。但是,我已经被锁定在Spring,Tomcat,JBoss或Websphere上(更糟)。我可以避免的那些,我愿意。那些我无法忍受的东西。这是喜好问题。
2015年

3

我认为这是一种不良做法,是的。其他人指出了将所有数据访问代码保留在自己的层中的优点。您不必四处寻找,更容易优化和测试...但是即使在该层中,您也可以选择:使用ORM,使用sprocs或将SQL查询作为字符串嵌入。我会说,SQL查询字符串是迄今为止最糟糕的选择。

使用ORM,开发变得容易得多,并且不容易出错。使用EF,只需创建模型类即可定义模式(无论如何,您都需要创建这些类)。使用LINQ进行查询很容易-您经常会遇到两行C#,否则您需要编写和维护一个proc。IMO,这在生产率和可维护性方面具有巨大优势-更少的代码,更少的问题。但是,即使您知道自己在做什么,也会有性能开销。

Sproc(或函数)是另一个选择。在这里,您可以手动编写SQL查询。但是至少您可以保证它们是正确的。如果您使用.NET,则如果SQL无效,Visual Studio甚至会抛出编译器错误。这很棒。如果您更改或删除了某些列,并且某些查询变得无效,那么至少在编译期间您可能会找到有关它的信息。将存储过程保存在自己的文件中也更容易-您可能会得到语法高亮显示,自动完成..etc。

如果您的查询存储为存储过程,则还可以更改它们而无需重新编译和重新部署应用程序。如果您发现SQL中的某些内容已损坏,则DBA可以修复该问题而无需访问您的应用程序代码。通常,如果您的查询使用字符串文字,则dbas几乎无法轻松地完成其工作。

SQL查询作为字符串文字也会使您的数据访问C#代码的可读性降低。

根据经验,魔术常数是不好的。其中包括字符串文字。


允许DBA在不更新应用程序的情况下更改您的SQL,可以有效地将您的应用程序分为两个单独开发,单独部署的实体。现在,您需要管理它们之间的接口协定,同步相互依赖的更改的发布等。这可能是好事,也可能是坏事,但这远比“将所有SQL放在一个地方”要重要得多。的架构决策。
IMSoP

2

这不是反模式(此答案很危险地接近观点)。但是代码格式化很重要,SQL字符串应该进行格式化,以便与使用它的代码明显分开。例如

    string query = 
    @"SELECT foo, bar
      FROM table
      WHERE id = @tn";

7
最明显的问题是值42的来源问题。您是否将该值连接到字符串中?如果是这样,它是从哪里来的?如何清除它以减轻SQL注入攻击?为什么此示例未显示参数化查询?
克雷格

我只是想显示格式。抱歉,我忘记将其参数化。现在在参考msdn.microsoft.com/library/bb738521(v=vs.100).aspx
-rleir

+1表示格式化的重要性,尽管我坚持认为SQL字符串是一种代码味道,与格式化无关。
查尔斯·伯恩斯

1
您是否坚持使用ORM?当不使用ORM时,对于较小的项目,我使用包含嵌入式SQL的'shim'类。
rleir

SQL字符串绝对不是代码的味道。没有ORM,作为经理和架构师,出于维护和有时出于性能方面的考虑,我鼓励他们使用SP。
Sentinel

1

我不能告诉你你的同事是什么意思。但是我可以回答:

准备SQL语句有什么好处?

是的。建议使用带有绑定变量的准备好的语句来防止SQL注入,这是十多年来Web应用程序最大的安全风险。这种攻击是如此普遍,以至于将近十年前出现在网络漫画中,并且是时候部署有效的防御措施了。

...而最有效的防御查询字符串错误连接的方法并不是首先将查询表示为字符串,而是使用类型安全的查询API来构建查询。例如,这是我如何使用QueryDSL用Java编写查询的方法:

List<UUID> ids = select(person.id).from(person).fetch();

如您所见,这里没有单个字符串文字,这使得SQL注入无法实现。而且,在编写此代码时我已经完成了代码,如果我选择重命名id列,我的IDE可以对其进行重构。另外,通过询问我的IDE person访问变量的位置,我可以轻松地找到人员表的所有查询。哦,您是否注意到它比您的代码更短,更容易看?

我只能假设这样的东西也可用于C#。例如,我听说过有关LINQ的很棒的事情。

总而言之,将查询表示为SQL字符串使IDE难以有效地协助编写和重构查询,从编译时到运行时推迟语法和类型错误的检测,并且是SQL注入漏洞的一个诱因[1]。因此,是的,有充分的理由为什么人们可能不希望在源代码中使用SQL字符串。

[1]:是的,正确使用预处理语句也可以防止SQL注入。但是这个线程中的另一个答案很容易受到SQL注入的影响,直到评论者指出这一点并没有激发初级程序员在必要时正确使用它们的信心。


需要明确的是:使用准备好的语句不会阻止SQL注入。使用带有绑定变量的准备好的语句可以。可以使用从不受信任的数据构建的准备好的语句。
安迪·莱斯特

1

这里有很多很好的答案。除了已经说过的话,我还要补充一点。

我不认为使用内联SQL是反模式。但是我认为反模式是每个程序员都在做自己的事情。您必须作为一个团队来决定一个共同的标准。如果每个人都在使用linq,则可以使用linq,如果每个人都在使用视图和存储过程,则可以这样做。

始终保持开放的心态,不要落入教条。如果您的商店是“ linq”商店,则使用该商店。除了linq绝对不工作的那个地方。(但是您不应该有99.9%的时间。)

因此,我要做的是研究代码库中的代码,并检查您的同事如何工作。


+1好点。可支持性比大多数其他考虑因素更为重要,因此请不要太聪明:)也就是说,如果您是ORM商店,请不要忘记在某些情况下ORM不能解决问题,您确实需要编写直接SQL。不要过早地进行优化,但是在任何不平凡的应用程序中,有时都必须遵循这条路线。
mcottle

0

从数据库和其余代码之间的数据库交互层看,代码看起来像。如果确实如此,那不是反模式。

该方法是该层的一部分,即使OP的想法有所不同(普通数据库访问,也不能与其他任何东西混用)。它可能被错误地放置在代码中。此方法属于单独的类“数据库接口层”。将其放在实现非数据库活动的方法旁边的普通类中是一种反模式。

反模式是从其他随机代码位置调用SQL。您需要在数据库和其余代码之间定义一个层,但这并不是您必须绝对使用一些流行的工具来为您提供帮助。


0

在我看来,no不是反模式,但是您会发现自己要处理字符串格式间距,尤其是对于不能容纳一行的大型查询。

另一个优点是,在处理应根据特定条件构建的动态查询时,它会变得更加困难。

var query = "select * from accounts";

if(users.IsInRole('admin'))
{
   query += " join secret_balances";
}

我建议使用sql查询构建器


可能只是您使用速记,但盲目使用“ SELECT *”是一个非常糟糕的主意,因为您将带回不需要的字段(带宽!)它导致非常滑的坡度导致一个有条件的“ WHERE”子句,而这条道路直通性能地狱。
mcottle

0

对于具有快速部署管道的许多现代组织而言,可以在几分钟之内完成代码更改的编译,测试和发布,因此将SQL语句嵌入到应用程序代码中完全有意义。根据您的使用方式,这不一定是反模式。

如今,在组织中存在一个使用存储过程的有效案例,这些组织在生产部署周围有很多过程,可能涉及数周的QA周期等。在这种情况下,DBA团队可以通过优化来解决仅在初始生产部署之后出现的性能问题。存储过程中的查询。

除非您处于这种情况下,否则建议您将SQL嵌入到应用程序代码中。阅读此主题中的其他答案,以获取最佳解决方案的建议。


以下为背景原文

存储过程的主要优点是,您可以优化数据库性能,而无需重新编译和重新部署应用程序。

在许多组织中,只能在可能需要数天或数周的质量保证周期等之后才能将代码投入生产。如果您的系统由于使用模式的改变或表大小的增加等原因而导致数据库性能出现问题,那么很难通过硬编码查询来跟踪问题,而数据库将具有能够识别问题存储过程的工具/报告等。 。使用存储过程,可以在生产中对解决方案进行测试/确定基准,并且可以快速解决问题,而无需推送应用软件的新版本。

如果此问题在您的系统中不重要,则将SQL语句硬编码到应用程序中是完全正确的决定。


错误...........
Sentinel'Apr

您认为您的评论对任何人都有用吗?
bikeman868 '18

这是完全不正确的。假设我们有一个Web API2项目,其中EF作为数据访问而Azure Sql作为存储。不,让我们摆脱EF并使用手工制作的SQL.db模式存储在SSDT SQL Server项目中.db模式迁移涉及在将更改推送到测试之前,确保代码库已同步到db模式并经过充分测试Azure中的db。代码更改涉及推出到App Service部署插槽,这需要2秒。加上手工编写的SQL与存储过程相比,在性能上具有优势,尽管有“相反的智慧”,但总的来说。
Sentinel

就您而言,部署可能需要几秒钟,但对于许多人而言并非如此。我并不是说存储过程更好,只是在某些情况下它们具有某些优点。我的最后一句话是,如果这些情况不适用,则将SQL放入应用程序中是非常有效的。这与您在说什么相矛盾?
bikeman868 '18 -4-11

存储过程是手工制作的SQL?
bikeman868 '18 -4-12
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.