Fra*_*ers 9 sql sql-server asp.net asp.net-mvc sql-server-2008
在去年的大学里,我遇到了一些我发现存储过程非常有趣的东西.主要是减少事务管理,错误处理和安全性的代码重复.但我已经研究过它,无法在任何地方找到它.也许我不知道它叫什么,所以我会解释.
假设我有一个简单的表(忘记它的关系,只是一个表).我至少有5种可能的操作,即CRUD,其中R读取一行的详细信息,或者读取给定条件的行列表.再说一次,让我们不要对复杂的存储过程进行太多详细介绍,让我们假装,对于这个例子,我们只需要执行以下5个操作:
select * from sometable where some condition
,没有复杂),出于本示例的目的,可以使用包含2或3列的简单表,以及执行这些操作时可以使用的最简单的过程.
问题:
我的讲师在与我们谈论交易时,建议我们采取这些简单的程序,并按正常方式将它们分开.但是然后编写一个执行其中每个操作的主程序.这意味着我们有一个主程序,我们传递一个字符让我们说'C'用于创建等等,以及一系列决定运行哪个作业的条件.当然,我们应该将主从需要的细节传递给子proc,这意味着我们需要从用户那里获取它们作为参数.这个主程序已经开始听起来很复杂.
他的推理是,在主程序中进行事务处理,验证,错误处理和安全处理.然后使用所需的参数调用该master过程,该过程执行所有检查,并将参数传递给子过程.以下是一个例子.
简单的表,不要过多担心关系,键,索引,约束,触发器等.
对于以下两段代码,唯一真正相关的位是try ... catch之间的部分.其余的是主人的锅炉板.
以下是创建过程的代码:
ALTER proc [Actions].[AreaCreate]
--External Variables - Input
@AreaName varchar(50), @AreaAvailablity bit, @Description varchar(max),
--External Variables - Output
@NoOfRecords int output
as
--Internal Variables
declare @ErrorMessage varchar(max)
Begin try
insert [Tennis3rdYrMVC].[dbo].Areas
([AreaName], [AreaAvailablity], [Description])
values (@AreaName, @AreaAvailablity, @Description)
--Show # of records affected so you can detect nulls or empty lists
--and handle them as you choose in the code
set @NoOfRecords = @@ROWCOUNT
End try
Begin Catch
set @ErrorMessage = ERROR_MESSAGE()
raiserror(@ErrorMessage,16,1)
return ERROR_NUMBER()
End Catch
--All Ok
return 0
Run Code Online (Sandbox Code Playgroud)
下面是删除过程的另一段代码
ALTER proc [Actions].[AreaDelete]
--External Variables - Input
@Id int,
--External Variables - Output
@NoOfRecords int output
as
--Internal Variables
declare @ErrorMessage varchar(max)
begin try
delete from [Tennis3rdYrMVC].[dbo].Areas
where Id = @Id
--Show # of records affected so you can detect nulls or empty lists
--and handle them as you choose in the code
set @NoOfRecords=@@ROWCOUNT
end try
begin catch
set @ErrorMessage = ERROR_MESSAGE()
raiserror(@ErrorMessage,16,1)
return ERROR_NUMBER()
end catch
--All Ok
return 0
Run Code Online (Sandbox Code Playgroud)
最后,建议的,复杂的主程序.
ALTER proc [Admin].[AreaMaster]
--External Variables - Input
@JobType int, -- 1=Create, 2=Read 1, 3=Read Many, 4=Update, 5=Delete
--Master sprock uses null defaults because not every job requires every field to be present.
--i.e. We dont need to know the area name of an area we want to delete.
@Id int = null, @AreaName varchar(50) = null,
@AreaAvailablity bit = null, @Description varchar(max) = null,
--External Variables - Output
@NoOfRecords int = null output --Used to count the number of records affected where needed.
as
BEGIN
--Internal Variables
declare @ErrorMessage varchar(max)
declare @return_value int
-- SET NOCOUNT ON added to reduce network traffic and speed things up a little.
SET NOCOUNT ON;
/*
--VALIDATION
--Logic for ensuring all required values are entered should be done in processing below in
--the master sprock, NOT in the children (i.e. why check for valid id in 5 sprocks when you
--can do it here in one).
We will do all the processing needed to ensure valid and required values are entered where
needed.
--SECURITY
This is also where we put the security code required to stop SQL Injection and other
attacks, NOT in the child sprocks. The child sprocks would not be allowed to execute by
pages directly.
*/
--Once all validation is done, call relevant child sprocks
--Call AreaCreate Sprock
if(@JobType='1')
begin
exec @return_value = [Actions].[AreaCreate]
@AreaName = @AreaName,
@AreaAvailablity = @AreaAvailablity,
@Description = @Description,
@NoOfRecords = @NoOfRecords OUTPUT
--select @return_value 'Return Value'
if @return_value<>0
begin
raiserror('Error: Problem creating area.',16,0)
--rollback transaction
return 99
end
end
--Call AreaShowDetail Sprock
if(@JobType='2')
begin
exec @return_value = [Actions].[AreaShowDetail]
@Id = @Id,
@NoOfRecords = @NoOfRecords output
----Testing
--select 'Return Value' = @return_value
if @return_value<>0
begin
raiserror('Error: Problem reading area details.',16,0)
--rollback transaction
return 99
end
end
--Call AreaShowList Sprock
if(@JobType='3')
begin
exec @return_value = [Actions].[AreasShowList]
@NoOfRecords = @NoOfRecords output
----Testing
--select 'Return Value' = @return_value
if @return_value<>0
begin
raiserror('Error: Problem reading areas list.',16,0)
--rollback transaction
return 99
end
end
--Call AreaUpdate Sprock
if(@JobType='4')
begin
EXEC @return_value = [Actions].[AreaUpdate]
@Id = @Id,
@AreaName = @AreaName,
@AreaAvailablity = @AreaAvailablity,
@Description = @Description,
@NoOfRecords = @NoOfRecords OUTPUT
--select 'Return Value' = @return_value
if @return_value<>0
begin
raiserror('Error: Problem updating area.',16,0)
--rollback transaction
return 99
end
end
--Call AreaDelete Sprock
if(@JobType='5')
begin
exec @return_value = [Actions].[AreaDelete]
@Id = @Id,
@NoOfRecords = @NoOfRecords output
--select 'Return Value' = @return_value
if @return_value<>0
begin
raiserror('Error: Problem deleting area(s).',16,0)
--rollback transaction
return 99
end
end
--All Ok
return 0
END
Run Code Online (Sandbox Code Playgroud)
那复杂还是什么?现在想象另一个级别,如果你有几个表可以操作,将决定调用哪个子主服务器,并且这个想要更好名字的MasterMaster必须为每个表中的每个字段都有外部参数.但是,验证,安全性和事务代码将转移到该级别.
更重要的是,专业人士会这样做.最后,我使用MVC与实体框架.假设我有一个这样的数据库(我有),只允许通过这个主存储过程访问(它是).如何在EF和MVC中调用此类存储过程.更好的是,我将如何绕过EF,并以我的视图将理解的方式将数据导入我的控制器.我知道如何使用ASP.Net代码执行此操作.但不是在MVC中.
有什么建议.请随意告诉我这是完全疯狂的,因为它看起来像是很多工作(如果你有50张桌子(我这样做).想象一下,5个简单的操作,50个表= 250个子存储过程+ 50个次级大师,+ 5个子大师+ 1个大师.我的工作已经完成.是否有工具可以做到这一点或者至少为我生成模板?
总而言之,我的问题是:
关于这一点,我说的是一些简单的东西,比如SQL Server中已经右键单击一个过程并选择执行的东西,它会生成一些简单的代码来测试你的过程,或者像一个用于创建函数的代码.
感谢您阅读本文以及您可以提供的任何帮助.
从理论上讲,您的讲师提出了一个很好的概念.将某些类型视为顶级域模型的想法很有意义.了解如何保存他们的直系孩子的对象也非常有用.
除了理论之外,我不同意实施,主要是因为它(不必要的)冗长和缺乏灵活性.
存储过程很棒,但使用ORM(如EF)或任何自动化框架的好处之一是,您不必一遍又一遍地编写样板代码.多年来,我大大减少了我的应用程序中仅CRUD程序的数量,而是根据需要自动生成语句(同样,这是一个好的ORM的函数).
阅读
大多数应用程序需要以不同的方式查看相同的数据,并且通常有利的是具有无限数量的用于查询该数据的选项.也许你想要整个记录; 也许你想要一个领域.也许您想要一个对象层次结构级别,或者您希望将对象层次结构展平为视图模型.
多读
当我不写存储过程,我倾向于把我的精力投入到高度优化的存储过程来有效地浏览/搜索数据.
在大多数业务应用程序中,read-many最终成为数据的摘要视图.由于搜索,返回1000条窄/平记录是微不足道的; 但检索1000个复杂对象的完整层次结构效率极低,通常不需要.
公平地说,你的主proc可以返回一个合适的摘要视图,但我参考了我之前能够以多种方式查看数据的观点.说明单个读多行为是不必要的限制.
删除
删除有时是存储过程的良好候选者.将100条记录加载到业务层只是为了获取它们的ID并逐个发出DELETE是没有意义的.
交易
在我看来,最灵活的交易支持通常来自业务层.请参阅我的答案"交易应该在.NET或SQL Server中处理?":https://stackoverflow.com/a/12470061/453277
"人"是顶级模型的良好候选者,而"地址"可能是该模型的孩子.
一个促进所有访问的主程序的想法是完全不切实际的.虽然您可能只向用户显示人员管理屏幕,但您可能希望在不需要或不知道与其相关的人员的情况下访问Address对象.
一个简单的例子是带有地址列表的人员简介页面; 单击"编辑"按钮启动模态窗口以编辑地址的详细信息.你真的想要通过人员管理逻辑来获取地址数据吗?您可以执行与父项相关的安全检查; 你可以创建一个与父母的新关系; 你可能什么都不做.灵活性和可重用性是关键.
此外,当您决定在Person-> Address关系之外添加Company-> Address关系时,您不希望重新创建任何此逻辑.
两个相关对象之间并不总是存在可定义的父/子关系.我最喜欢的例子是人物和文件.
哪一个是父母?
对于数据库来说,它很少重要.可能有一个PersonDocument
表有两个整数列的表; 哪个是父母并不重要.同样,在业务层中,灵活性是关键.您应该能够使用Document
"孩子"列表中的人员访问,并将Person
文档列表作为"子女"访问.
我喜欢将所有关系视为潜在的双向关系.将它们视为双向是永远不会伤害的,但强制父/子层次结构可能会受到限制.
SQL注入
我在你的master proc的注释中注意到了这一点:
这也是我们放置停止SQL注入和其他攻击所需的安全代码的地方,而不是在子级sprocks中.子进程不允许直接通过页面执行子进程.
应始终对存储过程的调用进行适当的参数化,从而提供所需的注入保护.这里提到的SQL注入要么无关紧要,要么令人担忧,因为它表明调用没有做好准备.
Securables
对SQL Server中对象级安全性的讨论远不在本文的讨论范围之内,但我要提到的是,您可以在不使用主过程的情况下实现极其精细的安全性.
如果您正在使用ORM(如EF),请让它完成其工作并为您编写此管道代码.不要试图强迫不同的范例.如果您必须使用讲师的方法(例如,对于作业),可能更容易从等式中删除EF.
灵活性是关键(我第三次说过).无论你开发的范例多么伟大,你都需要在某种程度上扩展它或偏离它.能够做到这一点至关重要.
即使两种类型看似无关,您也应该能够在相同的事务和逻辑上下文中操作它们.
正如@Habibillah指出的那样,不要忘记测试/可测试性.
当您需要交付项目时(即使用现有工具完成工作),不要重新发明轮子.但是,这是一个很好的问题,创建自己的对象持久性/检索方法是一个很好的学术练习.
归档时间: |
|
查看次数: |
2870 次 |
最近记录: |