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是用源代码编写的。我没有机会问为什么,他现在已经离开了两个星期(也许还有更多)。我认为他的意思是:
使用LINQ
或
将存储过程用于SQL
我正确吗?在源代码中编写SQL是否被视为反模式?我们是一个从事此项目的小组。我认为存储过程的好处是SQL开发人员可以参与开发过程(编写存储过程等)。
编辑
以下链接讨论了硬编码的SQL语句:https: //docs.microsoft.com/zh-CN/sql/odbc/reference/develop-app/hard-coded-sql-statements。准备SQL语句有什么好处?
#1 楼
为简单起见,您排除了关键部分。存储库是持久性的抽象层。我们将持久性分离到其自己的层中,以便我们可以在需要时更轻松地更改持久性技术。因此,在持久层之外使用SQL完全可以避免使用单独的持久层的努力。:在SQL特定的持久层内,SQL很好(例如,SQL是在
SQLCustomerRepository
中可以,但不能在MongoCustomerRepository
中)。在持久层之外,SQL破坏了您的抽象,因此对我来说,这是非常糟糕的做法。对于LINQ或JPQL之类的工具:那些工具只能在那里提取SQL的风味。在存储库外部使用LINQ-Code或JPQL查询会像原始SQL一样破坏持久性抽象。
单独的持久性层的另一个巨大优势是,它允许您进行单元测试您的业务逻辑代码,而无需设置数据库服务器。
您获得的内存配置文件低,快速的单元测试,在您的语言支持的所有平台上都具有可重复的结果。
一个MVC + Service架构,这是一个简单的任务,它是模拟存储库实例,在内存中创建一些模拟数据并定义当调用某个getter时存储库应返回该模拟数据。
然后您可以定义测试数据
测试写入数据库非常简单:验证持久层上的相关更新方法已被调用,并声明实体位于数据库中。发生这种情况时的正确状态。
评论
在绝大多数现实世界项目中,“我们可以改变持久性技术”的想法是不现实和不必要的。 SQL /关系数据库与NoSQL数据库(例如MongoDB)完全不同。请参阅此详细文章。最多,一个开始使用NoSQL的项目最终将意识到他们的错误,然后切换到RDBMS。以我的经验,允许从业务代码中直接使用面向对象的查询语言(例如JPA-QL)可提供最佳结果。
–罗杰里奥
17年5月16日在3:24
如果更改持久层的机会很小,该怎么办?如果在更改持久层时没有一对一的映射怎么办?额外的抽象级别有什么优势?
–pllee
17年5月16日在3:46
@Rogério正如您所说,从NoSQL存储迁移到RDBMS,反之亦然,这是不切实际的(假设技术选择是合适的)。但是,我参与了许多实际项目,我们从一个RDBMS迁移到另一个。在那种情况下,封装的持久层绝对是一个优势。
–大卫
17年5月16日在13:37
“在绝大多数现实世界项目中,“我们可以改变持久性技术”的想法是不现实和不必要的。”我公司以前是按照这种假设编写代码的。我们必须重构整个代码库,以不支持一个,而是支持三个完全不同的存储/查询系统,并且正在考虑使用第三个和第四个。更糟糕的是,即使我们只有一个数据库,缺乏整合也会导致各种代码错误。
–NPSF3000
17年5月17日在1:22
@Rogério您知道“现实世界中的绝大多数项目”吗?在我的公司中,大多数应用程序都可以使用许多常见的DBMS之一,这非常有用,因为大多数我们的客户确实对此有非功能性要求。
– BartoszKP
17年5月17日在11:27
#2 楼
当今,大多数标准业务应用程序使用具有不同职责的不同层。但是,您在应用程序中使用的是哪一层,以及哪一层由您和您的团队负责。在决定将SQL直接放置在已显示给我们的函数中是对还是错之前,您需要了解应用程序中的哪一层负有什么责任
上面的功能来自哪一层?
没有“一刀切”的解决方案。在某些应用程序中,设计人员更喜欢使用ORM框架,并让该框架生成所有SQL。在某些应用程序中,设计人员倾向于将此类SQL仅存储在存储过程中。对于某些应用程序,存在SQL所在的手写持久性(或存储库)层,而对于其他应用程序,可以通过将SQL严格放置在该持久性层中来在某些情况下定义异常。因此,您需要考虑的是:特定应用程序中需要或需要哪些层,以及您希望如何承担责任?您写了“我通常会有一个存储库层”,但是您要在该层中承担什么确切的职责,以及您想将哪些职责放到其他地方?首先回答,然后您可以自己回答问题。
评论
我已经编辑了问题。不确定是否可以照亮。
– w0051977
17年5月14日18:00
@ w0051977:您发布的链接不会告诉我们有关您的申请的任何信息,对吗?似乎您正在寻找一条简单的,行之有效的规则,在哪里放置SQL,该规则适合每个应用程序。空无一人。这是您必须针对您的单个应用程序(可能与您的团队一起)做出的设计决策。
–布朗博士
17年5月14日在18:37
+1。特别要指出的是,就某种方式的“硬编码”或可重用性,可维护性等方面而言,方法中的SQL与存储过程中的SQL之间并没有真正的区别。程序可以用一种方法来完成。当然,过程可能是有用的,但死记硬背的规则“我们不能在方法中使用SQL,因此让我们将其全部放入过程中”可能只会产生很多麻烦,却几乎没有好处。
–user82096
17年5月15日在16:48
@ user82096我要说的是,将数据库访问代码放入SP中通常对我所见过的几乎每个项目都是有害的。 (MS堆栈)除了实际的性能下降(严格的执行计划缓存)之外,通过分散逻辑来使系统维护更加困难,并且由与应用逻辑开发者不同的一组人员来维护SP。尤其是在Azure上,部署槽之间的代码更改就像工作的几秒钟一样,但是在槽之间进行非代码优先/数据库优先的架构迁移却有点麻烦。
–前哨
18年4月11日在7:21
#3 楼
Marstato给出了一个很好的答案,但我想添加一些注释。源代码中的SQL不是反模式,但是会引起问题。我记得您以前必须将SQL查询放入放置在每种表单中的组件的属性中。这使事情变得非常丑陋,而且非常麻烦,因此您不得不跳过障碍来查找查询。我坚决主张尽可能在我使用的语言范围内集中数据库访问。您的同事可能会遇到暗淡的回忆。
现在,有些评论正在谈论供应商锁定,因为这自然是一件坏事。不是。如果我每年要签署一张六位数的支票以使用Oracle,那么您可以打赌,我希望访问该数据库的任何应用程序都适当地使用额外的Oracle语法,但要最大程度地使用它。如果有一种“ Oracle方式”编写不会破坏数据库的SQL,那么我的闪亮数据库却被编码人员严重破坏了普通ANSI SQL的代码破坏了,我将不会感到高兴。是的,更改数据库将更加困难,但是我只看到它在大型客户站点上进行了20多年,却有几次是从DB2-> Oracle迁移而来的,因为托管DB2的大型机已过时并且已退役。 。是的,这是供应商的锁定,但是对于公司客户而言,实际上需要购买价格昂贵的功能强大的RDBMS(例如Oracle或Microsoft SQL Server),然后充分利用它。您有一份支持协议作为您的舒适毯。如果我要为具有丰富的存储过程实现的数据库付费,则希望将其用于有意义的情况。
这引出了下一点,如果您正在编写访问SQL数据库的应用程序,则您必须学习SQL以及其他语言,通过学习,我的意思是查询优化。如果您编写的SQL生成代码会使用几乎完全相同的查询冲刷SQL缓存,那么我会为您感到恼火。当您可以使用一个巧妙的参数化查询时。
没有任何借口,没有躲在Hibernate墙后面。错误使用的ORM确实会削弱应用程序的性能。我记得几年前曾在一个堆栈溢出问题上遇到过这样的问题:
在Hibernate中,我遍历250,000条记录,检查几个属性的值
,并更新那些符合某些
条件。它的运行速度有点慢,我该怎么做才能加快速度?
“更新表SET field1 =
即当不适合使用它时,请忽略休眠。理解数据库。
因此,总而言之,将SQL嵌入代码中可能是一种不好的做法,但是最终有很多事情可能会导致您尝试避免嵌入SQL。
评论
我没有说“供应商锁定”是坏事。我说过,这需要权衡。在具有xaaS的db的当今市场中,用于运行自托管db的旧合同和许可证将越来越少。与十年前相比,如今将数据库从供应商A更改为B相当容易。如果您阅读了评论,则不赞成利用数据存储的任何功能。因此,很容易将一些特定的供应商详细信息放到某些与供应商无关的东西(应用程序)中。我们努力遵循SOLiD,直到最后将代码锁定到单个数据存储中。没有大碍
– Laiv
17年5月15日在6:26
这是一个很好的答案。如果编写了可能的最差代码,通常正确的方法是导致最坏情况的方法。最糟糕的ORM代码可能比最糟糕的嵌入式SQL糟糕得多。
– jwg
17年5月15日在9:19
@Laiv优点PaaS有点改变游戏规则-我将不得不更多地考虑其含义。
– mcottle
17年5月16日在0:23
@jwg我倾向于说高于平均水平的ORM代码比低于平均水平的嵌入式SQL更糟糕:)
– mcottle
17年5月16日在0:27
@macottle假设ppl编写的嵌入式SQL的平均水平高于编写ORM的平均水平。我非常怀疑。许多开发人员没有能力先写左JOINs,然后先检查S.O。
– Laiv
17年5月17日在3:48
#4 楼
是的,将SQL字符串硬编码到应用程序代码中通常是一种反模式。让我们尝试摒弃我们多年来在生产代码中看到的容忍度。在同一文件中混合使用不同语法的完全不同的语言通常不是一种理想的开发技术。这与Razor等模板语言不同,后者旨在为多种语言提供上下文含义。正如Sava B.在下面的评论中提到的那样,您的C#或其他应用程序语言(Python,C ++等)中的SQL就像其他字符串一样,并且在语义上是没有意义的。
在混合多种语言时也是如此。在大多数情况下,尽管显然在某些情况下,这样做是可以接受的,例如C中的内联汇编,HTML中的CSS片段小而易于理解(请注意CSS旨在与HTML混合),等等。
(罗伯特·C·马丁(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与周围的应用程序代码对齐的缩进字符都会通过网络传输。对于小型应用程序来说,这可能微不足道,但是随着软件使用量的增加,它可能会加在一起。我对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)和平台,不同的方法可能更合适。
此方法和其他方法以下列方式解决上面的列表:
数据库代码易于定位,因为它已经集中。在较大的项目中,将like-SQL分组到单独的文件中,也许在名为
SQL
的文件夹下。支持第二个,第三个等数据库更容易。创建一个接口(或其他语言抽象)以返回每个数据库的唯一语句。每个数据库的实现只不过是类似于以下语句的语句:
return SqlResource.DoTheThing;
是的,这些实现可以跳过资源并将SQL包含在字符串中,但是上面的某些(并非全部)问题仍然会浮出水面。重构很简单- -只需重用相同的资源。您甚至可以在大多数时间通过几个格式语句将同一资源条目用于不同的DBMS系统。我经常这样做。
使用辅助语言可以使用描述性名称,例如
sql.GetOrdersForAccount
而不是更加晦涩难懂SELECT ... FROM ... WHERE...
SQL语句无论其大小和复杂性都用一行来表示。
SQL可以在SSMS和SQL Developer等数据库工具之间复制和粘贴,而无需进行修改或仔细操作复制。没有引号。没有结尾的反斜杠。特别是在Visual Studio资源编辑器的情况下,单击一下即可突出显示SQL语句。 Ctrl + C,然后将其粘贴到SQL编辑器中。
在资源中创建SQL很快,因此几乎没有动力将资源使用情况与代码中的SQL混合。
不管选择哪种方法,我都发现混合语言通常会降低代码质量。我希望这里描述的一些问题和解决方案可以帮助开发人员在适当的时候消除这种代码异味。
评论
我认为这过于关注在源代码中使用SQL的不良批评。在同一文件中使用两种不同的语言不一定很糟糕。它取决于很多事情,例如它们如何关联以及如何呈现。
– jwg
17年5月15日9:00
“在同一文件中混合使用不同语法的完全不同的语言”这是一个可怕的论点。它也将适用于Razor视图引擎以及HTML,CSS和Javascript。爱你的图画小说!
–伊恩·纽森(Ian Newson)
17年5月15日在12:54
这只是将复杂性推到了其他地方。是的,您的原始代码更简洁,但以添加开发人员现在出于维护目的而必须导航至的间接访问为代价。您这样做的真正原因是,如果在代码中的多个位置使用了每个SQL语句。这样,它实际上与任何其他普通重构(“提取方法”)没有什么不同。
–罗伯特·哈维(Robert Harvey)
17年5月15日在16:25
更清楚地说,这不是对“我如何处理我的SQL字符串”问题的答案,而是对以下问题的回答:“我如何处理足够复杂的字符串的任何集合”。您的答案雄辩地讲了。
–罗伯特·哈维(Robert Harvey)
17年5月15日在16:36
@RobertHarvey:我确信我们的经历会有所不同,但是就我个人而言,我发现有关的抽象在大多数情况下是一个胜利。多年来,我肯定会完善我的抽象权衡,也许有一天有可能将SQL重新添加到代码中。 YMMV。 :)我认为微服务可能很棒,它们通常也会将您的SQL详细信息(如果完全使用SQL)与核心应用程序代码分开。我不是在提倡“使用我的特定方法:资源”,而是在提倡“通常不要混合使用石油和水的语言”。
–Charles Burns
17年5月15日在19:29
#5 楼
这取决于。有许多种不同的方法可以起作用:对SQL进行模块化,并将其隔离为一组单独的类,函数或您的范式使用的任何抽象单元,然后调用
将所有复杂的SQL移至视图中,然后仅在应用程序逻辑中执行非常简单的SQL,因此您无需对任何模块进行模块化。
使用对象关系映射库。
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尽可能简单。批评者会告诉您,这是“不可思议”的水平,您应该直接从应用程序直接执行该过程。我想说,如果您的存储过程看起来或表现得很像INSERT
,UPDATE
或DELETE
,那么这是一个更好的主意,如果它正在做其他事情,则是一个更差的主意。最终,您必须自己决定哪种样式更有意义。(4)是非解决方案。对于小型项目或仅偶尔与数据库交互的大型项目,这可能是值得的。但是对于具有大量SQL的项目来说,这不是一个好主意,因为您可能会随意将同一查询的重复或变体散布在应用程序中,这会影响可读性和重构。
评论
所写的2实质上是假设一个只读数据库。存储过程过去在修改查询中并不罕见。
– jpmc26
17年5月16日,0:32
@ jpmc26:我会在括号内写上文章...您是否有改善此答案的具体建议?
–凯文
17年5月16日在0:45
嗯很抱歉在完成答案之前发表评论,然后分心。但是,可更新的视图使跟踪表的更新位置变得相当困难。无论采用何种机制,将更新限制为直接在表上进行,在理解代码逻辑方面确实具有其优势。如果将逻辑限制在数据库中,那么没有存储过程就无法实施。它们还具有允许通过一次调用进行多个操作的优点。底线:我不认为你应该对他们这么刻薄。
– jpmc26
17年5月16日,1:12
@ jpmc26:公平。
–凯文
17年5月16日在1:16
#6 楼
用源代码编写SQL是否被视为反模式?
不一定。如果您在此处阅读所有注释,则将在源代码中找到用于硬编码SQL语句的有效参数。
问题出在放置语句的位置。如果将SQL语句放置在整个项目的各处,那么您可能会忽略我们通常会努力遵循的某些SOLID原则。
假设他的意思是;之一:
1)使用LINQ
或
2)对SQL使用存储过程
我们不能说他的意思。但是,我们可以猜测。例如,我想到的第一个是供应商锁定。硬编码SQL语句可能会使您将应用程序紧密耦合到数据库引擎。例如,使用供应商的不符合ANSI标准的特定功能。
这不一定是错误的,也不是坏的。我只是在指出事实。
忽略SOLID原则和供应商锁定可能会导致您可能忽略的不利后果。
这就是为什么通常与团队坐在一起并暴露您的疑虑的原因。
我认为存储过程的好处在于SQL开发人员可以
参与开发过程(编写存储过程等)。
我认为它与存储过程的优点无关。此外,如果您的同事不喜欢硬编码的SQL,那么很可能将业务转移到存储过程也会对他不满意。
编辑:以下链接讨论硬编码的SQL
语句:https://docs.microsoft.com/zh-cn/sql/odbc/reference/develop-app/hard-coded-sql-statements。
准备SQL语句有什么好处吗?
是。该职位列举了准备好的陈述的优点。这是一种SQL模板。比字符串连接更安全。但是该帖子既不鼓励您采用这种方式,也不鼓励您确认自己是对的。
它只是说明了我们如何以安全有效的方式使用硬编码的SQL。
总结一下,请先咨询您的同事。发送邮件,给他打电话,...
无论他是否回答,请与团队坐在一起,揭露您的疑虑。寻找最适合您需求的解决方案。不要根据您从那里读到的内容做出错误的假设。
评论
谢谢。对于“对SQL语句进行硬编码可能会使您将应用程序紧密耦合到db引擎” +1。如果他是指SQL Server而不是SQL,即使用SQLConnection而不是dbConnection,我会徘徊; SQLCommand而不是dbCommand和SQLDataReader而不是dbDataReader。
– w0051977
17年5月14日在21:35
否。我指的是不符合ANSI的表达式的用法。例如:select GETDATE(),...。GETDATE()是SqlServer的日期函数。在其他引擎中,该函数具有不同的名称,不同的表达式,...
– Laiv
17年5月15日在3:46
“供应商锁定” ...人员/企业实际上多久将其现有产品的数据库迁移到另一个RDBMS?即使在仅使用ORM的项目中,我也从未见过这种情况:通常,软件也需要从头开始重写,因为两者之间的需求有所变化。因此,对我来说,考虑这一点似乎很像“过早的优化”。是的,这完全是主观的。
–奥利维尔·格雷戈尔(OlivierGrégoire)
17年5月15日在16:01
我不在乎多久发生一次。我很在乎这可能会发生。在未开发的项目中,从数据库中提取DAL的实现成本几乎为0。可能的迁移并非如此。反对ORM的论点很弱。 ORM不像15年前的Hibernate一样绿色。我所看到的是很多“默认”配置和相当糟糕的数据模型设计。就像许多其他工具一样,许多人都在做“入门”教程,而他们并不在乎幕后发生的事情。该工具不是问题。问题在于谁不知道如何以及何时使用它。
– Laiv
17年5月15日在17:54
另一方面,锁定供应商并不一定是不好的。如果被问到我会说我不喜欢。但是,我已经被锁定在Spring,Tomcat,JBoss或Websphere上(更糟)。我可以避免的那些,我愿意。那些我无法忍受的东西。这是喜好问题。
– Laiv
17年5月15日18:00
#7 楼
我认为这是一种不良做法,是的。其他人指出了将所有数据访问代码保留在自己的层中的优点。您不必四处寻找,更容易优化和测试...但是即使在该层中,您也可以选择:使用ORM,使用sprocs或将SQL查询作为字符串嵌入。我会说,SQL查询字符串是迄今为止最糟糕的选择。使用ORM,开发变得更加容易,并且不容易出错。使用EF,您只需创建模型类即可定义架构(无论如何您都需要创建这些类)。使用LINQ进行查询很容易-您经常会遇到两行C#,否则您需要编写和维护一个Sproc。 IMO,这在生产率和可维护性方面具有巨大优势-更少的代码,更少的问题。但是,即使您知道自己在做什么,也会有性能开销。
Sproc(或函数)是另一个选择。在这里,您可以手动编写SQL查询。但是至少您可以保证它们是正确的。如果您使用.NET,则如果SQL无效,Visual Studio甚至会抛出编译器错误。这很棒。如果您更改或删除了某些列,并且某些查询变得无效,那么至少在编译期间您可能会找到有关它的信息。将存储过程保存在自己的文件中也更容易-您可能会得到语法高亮显示,自动完成..etc。
如果您的查询存储为sproc,则还可以更改它们而无需重新编译和重新部署应用程序。如果您发现SQL中的某些内容已损坏,则DBA可以修复该问题而无需访问您的应用程序代码。通常,如果查询使用字符串文字,则dbas几乎无法轻松地完成其工作。
SQL查询作为字符串文字也会使您的数据访问C#代码可读性降低。
凭经验,魔术常数是不好的。其中包括字符串文字。
评论
允许DBA在不更新应用程序的情况下更改您的SQL,可以有效地将您的应用程序分为两个单独开发,单独部署的实体。现在,您需要管理它们之间的接口协定,同步相互依赖的更改的发布等。这可能是好事,也可能是坏事,但这远比“将所有SQL放在一个地方”要重要得多。达成的架构决策。
–IMSoP
17年9月20日在17:29
#8 楼
这不是反模式(此答案很危险地接近观点)。但是代码格式化很重要,并且应该对SQL字符串进行格式化,以使其与使用它的代码明显分开。例如 string query =
@"SELECT foo, bar
FROM table
WHERE id = @tn";
评论
最明显的问题是值42的来源问题。您是否将该值连接到字符串中?如果是这样,它是从哪里来的?如何清除它以减轻SQL注入攻击?为什么此示例未显示参数化查询?
– Craig
17年5月15日在17:19
我只是想显示格式。抱歉,我忘记对其进行参数化。现在在参考msdn.microsoft.com/library/bb738521(v=vs.100).aspx后已修复
– rleir
17年5月16日在6:49
+1代表格式化的重要性,尽管我坚持认为SQL字符串是一种代码味道,而与格式化无关。
–Charles Burns
17年5月17日在16:45
您是否坚持使用ORM?当不使用ORM时,对于较小的项目,我使用包含嵌入式SQL的'shim'类。
– rleir
17年5月21日在5:18
SQL字符串绝对不是代码味道。没有ORM,作为经理和架构师,出于维护和有时出于性能方面的考虑,我会鼓励他们使用SP。
–前哨
18年4月12日在6:17
#9 楼
我不能告诉你你的同事是什么意思。但是我可以回答:是准备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注入。使用带有绑定变量的准备好的语句可以。可以使用根据不受信任的数据构建的准备好的语句。
–安迪·莱斯特(Andy Lester)
17年5月18日在18:13
#10 楼
这里有很多很棒的答案。除了已经说过的话,我还想添加另一件事。
我不认为使用内联SQL是反模式。但是我认为反模式是每个程序员都在做自己的事情。您必须作为一个团队来决定一个共同的标准。如果每个人都在使用linq,那么您将使用linq,如果每个人都在使用视图和存储过程,则可以这样做。
始终保持开放的态度,不要落入教条。如果您的商店是“ linq”商店,则使用该商店。除了linq绝对不起作用的那个地方。 (但是您不应有99.9%的时间是您。)
所以我要做的是研究代码库中的代码,并检查您的同事的工作方式。
评论
+1好点。可支持性比大多数其他考虑因素更为重要,因此请不要太聪明:)也就是说,如果您是ORM商店,请不要忘记在某些情况下ORM不能解决问题,您确实需要编写直接SQL。不要过早地进行优化,但是在任何不平凡的应用程序中,有时都必须遵循这条路线。
– mcottle
17年9月20日在3:55
#11 楼
从数据库和其余代码之间的数据库交互层看,代码看起来像。如果确实如此,那不是反模式。即使OP采取不同的想法(普通数据库访问,也不能与其他任何东西混合使用),该方法还是该层的一部分。它可能被错误地放置在代码中。此方法属于单独的类“数据库接口层”。将其放置在实现非数据库活动的方法旁边的普通类内将是一种反模式。
反模式将是从其他随机的代码位置调用SQL。您需要在数据库和其余代码之间定义一个层,但这并不是您必须绝对使用一些流行的工具来为您提供帮助。
#12 楼
我认为no不是反模式,但是您会发现自己要处理字符串格式间距,特别是对于不能容纳一行的大型查询。另一个优点是,处理起来会更困难应该根据特定条件构建的动态查询。
var query = "select * from accounts";
if(users.IsInRole('admin'))
{
query += " join secret_balances";
}
我建议使用sql查询构建器
评论
可能是您只是使用速记,但盲目使用“ SELECT *”是一个非常糟糕的主意,因为您将带回不需要的字段(带宽!)而且尽管我对条件Admin子句没有任何疑问它导致非常滑的坡度导致一个有条件的“ WHERE”子句,而这条道路直接通往性能地狱。
– mcottle
17年9月18日在8:33
#13 楼
对于具有快速部署管道的许多现代组织而言,可以在几分钟之内完成代码更改的编译,测试和发布,因此将SQL语句嵌入到应用程序代码中完全有意义。这不一定是反模式,具体取决于您的使用方式。当今使用存储过程的有效案例是在组织中,围绕生产部署有很多过程的组织可能涉及数周的时间。质量保证周期等。在这种情况下,DBA团队可以通过优化存储过程中的查询来解决仅在初始生产部署后出现的性能问题。除非您处于这种情况,否则建议您嵌入您的SQL到您的应用程序代码。阅读此线程中的其他答案,以获取有关实现此问题的最佳方法的建议。
以下上下文原始文本
存储过程的主要优点是:您可以在不重新编译和重新部署应用程序的情况下优化数据库性能。
在许多组织中,代码只能在QA周期等(可能需要数天或数周)之后才能投入生产。如果您的系统由于使用模式的更改或表大小的增加等原因而导致数据库性能出现问题,那么很难通过硬编码查询来跟踪问题,而数据库将具有可识别问题存储过程的工具/报告等。 。
使用存储过程,可以在生产环境中对解决方案进行测试/基准测试,并且可以快速解决该问题,而无需推送应用软件的新版本。
如果此问题在您的系统,那么将SQL语句硬编码到应用程序中是完全正确的决定。
评论
错误...........
–前哨
18年4月11日在6:01
您认为您的评论对任何人都有用吗?
–bikeman868
18年4月11日在6:03
这是完全不正确的。假设我们有一个Web API2项目,其中EF作为数据访问而Azure Sql作为存储。不,让我们摆脱EF并使用手工SQL.db模式存储在SSDT SQL Server项目中.db模式迁移需要在将更改推送到测试之前确保代码库已同步到db模式并经过了充分测试Azure中的db。代码更改涉及推出到App Service部署插槽,这需要2秒。加上手工编写的SQL与存储过程相比,在性能上有优势,尽管有“相反的智慧”,但总的来说。
–前哨
18年4月11日在7:16
就您而言,部署可能需要几秒钟,但对于许多人而言并非如此。我并不是说存储过程更好,只是在某些情况下它们具有某些优点。我的最后一句话是,如果这些情况不适用,则将SQL放入应用程序中是非常有效的。这与您在说什么相矛盾?
–bikeman868
18年4月11日在7:23
存储过程是手工制作的SQL?
–bikeman868
18年4月12日在5:48
#14 楼
与使用SQL视图相比,是的,它是一种反模式。过去,我一直反对使用SQL。我曾经使用过ORM,并且到处都是LINQ的拥护者。我错了。
SQL视图的情况:
有些人半正确地讨论了对单独的持久层的需求。但是RDBMS已经是持久层,并且SQL已经相当标准化。 (我见过的项目中有一个数据库[DML],ORM [POCO],存储库模式,控制反转[接口],数据服务-共有6层“持久层”。少即是多。)
当您在RDBMS中定义VIEW时,它仍然与模式(以及该模式的版本)相邻。这是一件好事。
用于视图的版本控制与用于表的版本控制相同。如果您的ORM不支持View的迁移,请选择更好的视图,或者不使用ORM迁移,而应维护自己的主脚本(例如:如果不存在,请在表中添加列;替换视图)。
就像任何“重用”方案一样,将来可以编写另一个Web服务器以利用相同的数据库和视图。但是,同一网络上的另一个系统也可以集成和利用相同的视图,并且报表引擎可以使用这些视图。
Web Service应该仅作为适配器存在,以便浏览器中的客户端应用程序可以查询数据库。 (在实践中,人们也喜欢使用此类RPC内联实现额外的登录-但这是另一回事了)
那可模拟性又如何呢?逻辑应该在“视图”-“单元测试”中进行。如果您想进行更多的集成测试,那么根据定义,它也包括“数据库”:拥有一个完整的数据库测试环境。
考虑到以上几点,在代码中使用SQL更糟:
使用ORM,您可以指定SQL并为物料提供POCO。这样,您就可以获得类型安全性。但是,最好有一个VIEW并告诉ORM为您重建Pocos。对于View,ORM可以看到模式并导出正确的POCO。
如果要稍微调整一个Web Service的逻辑,则在代码中,则需要编译并升级。如果是整体式的,这将是乏味且冒险的。如果是微服务,这仍然很繁琐,并且依赖项仍然存在风险。但是,如果它是一个View,则可以轻松部署并确保它仅会影响View本身。
代码中的文字SQL很适合开发。但是仍然应该鼓励在View中对其进行原型设计,因为在查询工具中“测试”该功能非常容易-没错,直接在持久层中使用
源代码中的SQL不是反模式这是实际反模式的症状:
“不要将其放在RDBMS中” –这只是对年轻的编码人员的不满,这是不合理的。如果您是包含“数据库”的全栈开发人员或后端开发人员。
“全部用代码完成”-这可能是git成功的结果。当全栈编码器做出这种决定时,编码器会将其放入代码中。
评论
“使用Linq”和“使用存储过程”不是原因;它们只是建议。等待两个星期,然后问他原因。堆栈交换网络使用称为Dapper的微型ORM。我认为可以说,大多数Dapper代码是“硬编码SQL”(或多或少)。因此,如果这是一种不良做法,那么这是地球上最著名的Web应用程序之一采用的不良做法。
为了回答有关存储库的问题,无论您放置在哪里,硬编码的SQL仍然是硬编码的SQL。不同之处在于,存储库为您提供了封装硬编码SQL的位置。这是一个抽象层,从程序的其余部分隐藏了SQL的细节。
不,代码中的SQL不是反模式。但这对于一个简单的SQL查询来说是很多样板代码。
“在源代码中”和“在源代码中全部传播”之间是有区别的。