




已阅读5页,还剩30页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
在ASP.NET 2.0中操作数据之二十一:实现开放式并发作者:heker2007 字体:增加减小 类型:转载 时间:2016-05-07我要评论本文主要介绍ASP.NET 2.0如何实现开放式并发控制,并逐步讲解从数据访问层到业务逻辑层以及到最终的页面是如何一步一步操作的。导言对于那些仅仅允许用户查看数据,或者仅有一个用户可以修改数据的web应用软件,不存在多用户并发冲突的问题。然而对于那些允许多个用户修改或删除数据的web应用软件,则有可能发生一个用户所做的更改与另一个并发用户的更改冲突。在没有任何并发策略的地方,当两个用户同时编辑某一条记录,最后提交的用户的更改将覆盖先提交的用户所作的更改。例如,假设两个用户,Jisun和Sam,都访问我们的应用软件中的一个页面,这个页面允许访问者通过一个GridView控件更新和删除产品数据。他们都同时点击GridView控件中的Edit按钮。Jisun把产品名称更改为“Chai Tea”并点击Update按钮,实质结果是向数据库发送一个UPDATE语句,它将更新此产品的所有可修改的字段(尽管Jisun实际上只修改了一个字段:ProductName)。在这一刻,数据库中包含有这条产品记录“Chai Tea”种类为Beverages、供应商为Exotic Liquids、等该产品的详细信息。然而,在Sam的屏幕中的GridView里,当前编辑行里显示的产片名称依旧是“Chai”。在Jisun的更改被提交后片刻,Sam把种类更改为“Condiments”并点击Update按钮。这个发送到数据库的UPDATE语句的结果是将产品名称更改为“Chai”、CategoryID字段的值是种类Beverages对应的ID,等等。Jisun所作的对产品名称的更改就被覆盖了。图1展示了这些连续的事件。图 1: 当两个用户同时更新一条记录,则存在一个用户的更改覆盖另一个的更改的可能性类似地,当两个用户同时访问一个页面,一个用户可能更新的事另一个用户已经删除的记录。或者,在一个用户加载页面跟他点击删除按钮之间的时间里,另一个用户修改了这条记录的内容。有下面三中并发控制策略可供选择:1.什么都不做 如果并发用户修改的是同一条记录,让最后提交的结果生效(默认的行为)2.开放式并发(Optimistic Concurrency) - 假定并发冲突只是偶尔发生,绝大多数的时候并不会出现; 那么,当发生一个冲突时,仅仅简单的告知用户,他所作的更改不能保存,因为别的用户已经修改了同一条记录3.保守式并发(Pessimistic Concurrency) 假定并发冲突经常发生,并且用户不能容忍被告知自己的修改不能保存是由于别人的并发行为;那么,当一个用户开始编辑一条记录,锁定该记录,从而防止其他用户编辑或删除该记录,直到他完成并提交自己的更改注意:在本节里,我们不讨论保守式并附的例子。保守式并发控制很少使用,因为锁定如果没有完全释放,会妨碍其他用户进行数据更新。例如,如果一个用户为了编辑而锁定某一条记录,但在解锁之前就离开了,那么其他任何用户都不能更新这条记录,直到最初的用户返回并完成他的更新。因此,使用保守式并发控制的地方,相应地会作一个时间限制,如果到达这个时间限制,则取消锁定。例如订票网站,当用户完成他的订票过程时会锁定某个特定的座位,这就是一个使用保守式并发控制的例子。第一步:如何实现开放式并发控制开放式并发控制能够确保一条记录在更新或者删除时跟它开始这次更新或修改过程时保持一致。例如,当在一个可编辑的GridView里点击编辑按钮时,该记录的原始值从数据库中读取出来并显示在TextBox和其他Web控件中。这些原始的值保存在GridView里。随后,当用户完成他的修改并点击更新按钮,这些原始值加上修改后的新值发送到业务逻辑层,然后到数据访问层。数据访问层必定发出一个SQL语句,它将仅仅更新那些开始编辑时的原始值根数据库中的值一致的记录。图二描述了这些事件发生的顺序。图2: 为了更新或删除能够成功,原始值必须与数据库中相应的值一致有多种方法可以实现开放式并发控制(查看Peter A. Bromberg的文章 Optmistic Concurrency Updating Logic,从摘要中看到许多选择)。ADO.NET类型化数据集提供了一种应用,这只需要在配置时勾选上一个CheckBox。使用开发式并发的目的是使类型化数据集的TableAdapter的UPDATE和DELETE语句可以检测自该记录加载到DataSet中以来数据库中的值是否被更改。例如下面的UPDATE语句,当当前数据库中的值与GridView中开始编辑的原始值一致才更新某个产品的名称和价格。ProductName 和 UnitPrice参数包含的是用户输入的新值,而参数original_ProductName 和 original_UnitPrice则包含最初点击编辑按钮时加载到GridView中的值:?1234567UPDATE Products SETProductName = ProductName,UnitPrice = UnitPriceWHEREProductID = original_ProductID ANDProductName = original_ProductName ANDUnitPrice = original_UnitPrice注意:这个UPDATE语句是为了易读而简单化了。实际上,在WHERE子句中检测UnitPrice会比较棘手,这是因为UnitPrice可以包含空值,而NULL = NULL则总是返回False(相应地你必须用IS NULL)。除了使用一个不同的UPDATE语句之外,配置TableAdapter使用开放式并发控制还需要修改它直接发送到数据库的方法。回到我们的第一节,创建一个数据访问层,这些发送到数据库的方法接收一列标量的值作为输入参数(不仅仅是强类型DataRow或DataTable的实例)。当使用开放式并发,直接发送到数据库的Update() 和 Delete()方法就包含了对应原始值的输入参数。而且,业务逻辑层中批量方式更新的代码(Update()的重载,它不仅接受标量值,也接受DataRows 和 DataTables)也要做出相应的更改。与其扩展我们现有得数据访问层表适配器使用开放式并发(同时也必须修改业务逻辑层以协调),不如让我们创建一个新的类型化数据集NorthwindOptimisticConcurrency,在它里面我们添加一个使用开放式并发的Products表适配器。然后,我们将在业务逻辑层中创建类ProductsOptimisticConcurrencyBLL,它为了支持开放式并发的DAL会有适当的更改。一旦这些基础工作都已完成,我们就可以创建ASP.NET页面。第二步: 创建一个支持开放式并发的数据访问层为了创建一个新的类型化数据集,在App_Code文件夹里的DAL文件夹上右键点击,选择添加一个新的数据集并命名为NorthwindOptimisticConcurrency。正如我们在第一节中看到过的那样,系统会自动添加一个表适配器(TableAdapter)到当前的类型化数据集众,并自动地进入TableAdapter配置向导。在第一屏中,向导提示我们选择数据库连接 连接到同样的数据库Northwind并使用Web.config里设置好的连接字符串NORTHWNDConnectionString。图 3: 连接到同一个数据库Northwind下一步,向导提示我们选择如何访问数据库:通过一个指定的SQL语句,创建新的存储过程,或者使用一个现有的存储过程。既然我们最初的DAL是使用的是指定SQL查询语句,这里我们还是使用它。图4: 使用指定SQL语句的方式访问数据库下一步,进入查询分析器,返回产品信息。让我们使用在最初的DAL中产品TableAdapter相同的SQL查询,它返回产品的所有字段包括产品的供应商和类别名称。?123456789SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,(SELECT CategoryName FROM CategoriesWHERE Categories.CategoryID = Products.CategoryID)as CategoryName,(SELECT CompanyName FROM SuppliersWHERE Suppliers.SupplierID = Products.SupplierID)as SupplierNameFROM Products图5:使用在最初的DAL中产品TableAdapter相同的SQL查询在我们进入下一步之前,点击“高级选项”按钮。要让这个TableAdapter使用开放式并发,仅仅需要勾选上“使用开放式并发”。图6:勾选“使用开放式并发”启用开放式并发控制最后,需要指出的是,该TableAdapter应该同时使用“填充DataTable”和“返回DataTable”两种要生成的方法;并且,勾选“创建方法以将更新直接发送到数据库(GenerateDBDirectMethods)”。将返回DataTable的方法名称从GetData改为GetProducts,使之与我们最初的DAL中的命名规则匹配。图7:让这个TableAdapter利用所有的数据访问方式完成了配置向导后,该数据集设计器将包含一个强类型的Products DataTable和TableAdapter。让我们花些时间把该DataTable的名称Products改为ProductsOptimisticConcurrency,方法是右键点击DataTable的标题栏,从菜单中选择“重命名”。图8:一个DataTable和TableAdapter已经添加到类型化数据集为了看看ProductsOptimisticConcurrency TableAdapter(使用开放式并发)和Products TableAdapter(不使用并发控制)的UPDATE 和 DELETE查询之间有什么不同,选中该TableAdapter并转到属性窗口。在DeleteCommand 和 UpdateCommand 这两个属性的 CommandText 子属性里,我们可以看到调用DAL的update或者delete关联的方法时发送到数据库的实际的SQL语法。ProductsOptimisticConcurrency TableAdapter使用的DELETE语句是?123456789101112131415161718DELETE FROM ProductsWHERE (ProductID = Original_ProductID)AND (ProductName = Original_ProductName)AND (IsNull_SupplierID = 1 AND SupplierID IS NULL)OR (SupplierID = Original_SupplierID)AND (IsNull_CategoryID = 1 AND CategoryID IS NULL)OR (CategoryID = Original_CategoryID)AND (IsNull_QuantityPerUnit = 1 AND QuantityPerUnit IS NULL)OR (QuantityPerUnit = Original_QuantityPerUnit)AND (IsNull_UnitPrice = 1 AND UnitPrice IS NULL)OR (UnitPrice = Original_UnitPrice)AND (IsNull_UnitsInStock = 1 AND UnitsInStock IS NULL)OR (UnitsInStock = Original_UnitsInStock)AND (IsNull_UnitsOnOrder = 1 AND UnitsOnOrder IS NULL)OR (UnitsOnOrder = Original_UnitsOnOrder)AND (IsNull_ReorderLevel = 1 AND ReorderLevel IS NULL)OR (ReorderLevel = Original_ReorderLevel)AND (Discontinued = Original_Discontinued)相反,最初的DAL的Products TableAdapter所使用的DELETE语句则简单得多:?1DELETE FROM Products WHERE (ProductID = Original_ProductID)正如你所看到的,启用了开发式并发的TableAdapter所使用的DELETE语句里的WHERE子句包含了对表Product每一个字段现有的值与GridView(或者DetailsView,FormView)最后一次加载时的原始值的对比。因为除了ProductID,ProductName, 和Discontinued之外,其他所有字段都可能为NULL值,所以WHERE子句里还包含了额外的参数以及与NULL值恰当的比较。在这一节里,我们不会在启用了开放式并发的数据集里增加其他的DataTable了,因为我们的ASP.NET页面将仅提供更新和删除产品信息的功能。然而,我们仍然需要在ProductsOptimisticConcurrency TableAdapter里添加GetProductByProductID(productID) 方法。为了实现这一点,在TableAdapter的标题栏(在Fill和GetProducts方法名的上方)上右键并从菜单里选择“添加查询”。这将启动TableAdapter查询配置向导。在TableAdapter的最初配置的基础上,选择指定SQL语句来创建GetProductByProductID(productID)方法(见图四)。因为GetProductByProductID(productID)方法返回指定产品的信息,因此需要指定SQL查询类型为“SELECT(返回行)”。图9:标记SQL查询类型为“SELECT(返回行)”进入下一步,向导提示我们指定SQL语句,并且与载入TableAdapter默认查询语句。在现有的查询语句的基础上添加WHERE ProductID = ProductID子句,如图10:图10:在预载入的查询语句上添加WHERE子句从而返回特定的产品记录最后,把生成的方法重命名为FillByProductID和GetProductByProductID。图11:把生成的方法重命名为FillByProductID和GetProductByProductID完成这个向导之后,现在这个TableAdapter包含两个访问数据的方法:GetProducts(),它返回所有 的产品;和GetProductByProductID(productID),它返回特定的产品。第三步: 创建一个支持启用了开放式并发的DAL的业务逻辑层我们现有的ProductsBLL类包含批量更新和直接发送数据库的模式的例子。AddProduct方法和 UpdateProduct重载都使用了批量更新模式,通过一个ProductRow实例发送到TableAdapter的Update方法。另一方面,DeleteProduct方法则使用直接发送到数据库的模式,调用TableAdapter的Delete(productID)方法。在新的ProductsOptimisticConcurrency TableAdapter里,发送到数据库的方法现还要求传入原始的值。例如,Delete方法现在要求十个输入参数:原始的ProductID、ProductName、SupplierID、CategoryID、QuantityPerUnit、UnitPrice、UnitsInStock、UnitsOnOrder、ReorderLevel和Discontinued。它在发送到数据库的DELETE语句的WHERE子句里使用这些额外的输入参数,仅仅删除当前数据库的值与原始值一致的指定记录。使用批量更新模式时,如果标记给TableAdapter的Update使用的方法没有更改,那么代码就需要同时记录原始值和新的值。然而,与其在我们现有的ProductsBLL类的基础上试图使用启用了开放式并发的DAL,不如让我们重新创意一个业务逻辑类支持我们新的DAL。在App_Code文件夹下的BLL子文件夹里,添加一个名为ProductsOptimisticConcurrencyBLL的新类。图 12: 添加ProductsOptimisticConcurrencyBLL类到BLL文件夹然后,在ProductsOptimisticConcurrencyBLL类里添加如下代码:?123456789101112131415161718192021222324252627282930using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.WebControls.WebParts;using System.Web.UI.HtmlControls;using NorthwindOptimisticConcurrencyTableAdapters;System.ComponentModel.DataObjectpublic class ProductsOptimisticConcurrencyBLLprivate ProductsOptimisticConcurrencyTableAdapter _productsAdapter = null;protected ProductsOptimisticConcurrencyTableAdapter Adaptergetif (_productsAdapter = null)_productsAdapter = new ProductsOptimisticConcurrencyTableAdapter();return _productsAdapter;System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, true)public NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable GetProducts()return Adapter.GetProducts();注意在类的声明开始之前的using NorthwindOptimisticConcurrencyTableAdapters语句。命名空间NorthwindOptimisticConcurrencyTableAdapters包含了类ProductsOptimisticConcurrencyTableAdapter,它提供DAL的方法。并且,在类声明之前我们还能找到System.ComponentModel.DataObject属性标志,它指示Visual Studio把该类包含在ObjectDataSource向导的数据对象下拉列表中。类ProductsOptimisticConcurrencyBLL的Adapter属性提供快速访问ProductsOptimisticConcurrencyTableAdapter类的一个实例,并和我们最初的BLL类(ProductsBLL、CategoriesBLL等等)相似。最后,方法GetProducts()仅仅是调用DAL的GetProdcuts()方法并返回一个ProductsOptimisticConcurrencyDataTable对象,该对象由对应数据库里每一个产品记录的ProductsOptimisticConcurrencyRow实例组成。使用支持开放式并发的发送到数据库的模式删除一个产品记录当使用支持开放式并发的DAL发送到数据库的模式,方法必须传入新值和原始值。对删除来说,这没有新的值,所以仅仅需要传入原始值。那么,在我们的BLL里,我们必须接收所有原始值所为输入参数。让ProductsOptimisticConcurrencyBLL类的DeleteProduct方法使用这个发送到数据的方法。这意味着此方法必须接受所有的十个产品数据字段作为输入参数,并传送这些参数到DAL,如下面的代码所示:?12345678910111213141516171819202122System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Delete, true)public bool DeleteProduct(int original_productID, string original_productName,int? original_supplierID, int? original_categoryID,string original_quantityPerUnit, decimal? original_unitPrice,short? original_unitsInStock, short? original_unitsOnOrder,short? original_reorderLevel, bool original_discontinued)int rowsAffected = Adapter.Delete(original_productID,original_productName,original_supplierID,original_categoryID,original_quantityPerUnit,original_unitPrice,original_unitsInStock,original_unitsOnOrder,original_reorderLevel,original_discontinued);/ Return true if precisely one row was deleted, otherwise falsereturn rowsAffected = 1;如果这些在GridView(或者是DetailsView、FormView)最后一次加载时的原始值跟用户点击删除按钮时数据库中的值不一致, WHERE子句将不能匹配任何数据库记录,这就没有记录会受到影响。因此,TableAdapter的Delete方法将返回0并且BLL的DeleteProduct方法返回false。使用支持开放式并发的批量更新模式修改一个产品记录正如之前注意到的,批量更新模式时用的TableAdapter的Update方法也有同样的方法声明为不管是否支持开放式并发。也就是,Update方法可以接受一个DataRow,一批DataRow,一个DataTable,或者一个类型化的数据集。正是因为DataTable在它的DataRow(s)里保留了从原始值到修改后的值这个变化的轨迹使这成为可能。当DAL生成它的UPDATE语句时,参数original_ColumnName装入DataRow中的原始值,反之,参数ColumnName装入DataRow中修改后的值。在类ProductsBLL(我们最初使用的不支持开放式并发DAL的)里,当我们使用批量更新模式更新产品信息时,我们的代码执行的则是按顺序执行下列事件:1.使用TableAdapter的GetProductByProductID(productID)方法读取当前数据库中的产品信息到ProductRow实例2.在第1步里将新的值赋值到ProductRow实例3.调用TableAdapter的Update方法,传入该ProductRow实例这一连串的步骤,无论如何都不可能支持开放式并发,因为在第一步中产生的ProductRow是直接从数据库组装的,这意味着,DataRow中使用的原始值是当前存在于数据库中值,而并非开始编辑过程时绑定到GridView的值。相反地,当使用启用开放式并发的DAL,我们需要修改UpdateProduct方法的重载以使用下面这些步骤:1.使用TableAdapter的GetProductByProductID(productID)方法读取当前数据库中的产品信息到ProductsOptimisticConcurrencyRow实例2.在第1步里将原始 值赋值到ProductsOptimisticConcurrencyRow实例3.调用ProductsOptimisticConcurrencyRow实例的AcceptChanges()方法,这指示DataRow目前这些值是“原始”的值4.将新 的值赋值到ProductsOptimisticConcurrencyRow实例5.调用TableAdapter的Update方法,传入该ProductsOptimisticConcurrencyRow实例第1步读取当前数据库里指定产品记录的所有字段的值。对更新所有 产品字段的UpdateProduct的重载里,这一步是多余的(因为这些值在第2步中被改写),而对那些仅仅传入部分字段值的重载方法来说则是必要的。一旦原始值赋值到ProductsOptimisticConcurrencyRow实例,调用AcceptChanges()方法,这将当前DataRow中的值标记为原始值,这些值将用作UPDATE语句的original_ColumnNam参数。然后,新的参数值被赋值到ProductsOptimisticConcurrencyRow,最后,调用Update方法,传入这个DataRow。下面这些代码展示了重载方法UpdateProduct接受所有产品数据字段作为输入参数。虽然这里没有展示,实际上从本节教程下载的ProductsOptimisticConcurrencyBLL类里还包含了重载方法UpdateProduct,它仅仅接受产品名称和单价作为输入参数。?1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374protected void AssignAllProductValues(NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,string productName, int? supplierID, int? categoryID, string quantityPerUnit,decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,short? reorderLevel, bool discontinued)product.ProductName = productName;if (supplierID = null)product.SetSupplierIDNull();elseproduct.SupplierID = supplierID.Value;if (categoryID = null)product.SetCategoryIDNull();elseproduct.CategoryID = categoryID.Value;if (quantityPerUnit = null)product.SetQuantityPerUnitNull();elseproduct.QuantityPerUnit = quantityPerUnit;if (unitPrice = null)product.SetUnitPriceNull();elseproduct.UnitPrice = unitPrice.Value;if (unitsInStock = null)product.SetUnitsInStockNull();elseproduct.UnitsInStock = unitsInStock.Value;if (unitsOnOrder = null)product.SetUnitsOnOrderNull();elseproduct.UnitsOnOrder = unitsOnOrder.Value;if (reorderLevel = null)product.SetReorderLevelNull();elseproduct.ReorderLevel = reorderLevel.Value;product.Discontinued = discontinued;System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, true)public bool UpdateProduct(/ new parameter valuesstring productName, int? supplierID, int? categoryID, string quantityPerUnit,decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,short? reorderLevel, bool discontinued, int productID,/ original parameter valuesstring original_productName, int? original_supplierID, int? original_categoryID,string original_quantityPerUnit, decimal? original_unitPrice,short? original_unitsInStock, short? original_unitsOnOrder,short? original_reorderLevel, bool original_discontinued,int original_productID)/ STEP 1: Read in the current database product informationNorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable products =Adapter.GetProductByProductID(original_productID);if (products.Count = 0)/ no matching record found, return falsereturn false;NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product = products0;/ STEP 2: Assign the original values to the product instanceAssignAllProductValues(product, original_productName, original_supplierID,original_categoryID, original_quantityPerUnit, original_unitPrice,original_unitsInStock, original_unitsOnOrder, original_reorderLevel,original_discontinued);/ STEP 3: Accept the changesproduct.AcceptChanges();/ STEP 4: Assign the new values to the product instanceAssignAllProductValues(product, productName, supplierID, categoryID,quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel,discontinued);/ STEP 5: Update the product recordint rowsAffected = Adapter.Update(product);/ Return true if precisely one row was updated, otherwise falsereturn rowsAffected = 1;第四步: 从ASP.NET页面把原始值和新值传入BLL 方法完成了DAL和BLL后,剩下的工作就是创建一个能利用系统中内建的开放式并发逻辑的ASP.NET页面。特别地,数据 Web 服务器控件(GridView,DetailsView或FormView)必须记住它的原始值,并且ObjectDataSource必须同时传送这两套值到业务逻辑层。此外,ASP.NET页面必须加以配置从而适当地处理并发冲突。首先,打开EditInsertDelete文件夹中的OptimisticConcurrency.aspx页面,添加一个GridView控件到设计器,设置它的ID属性为ProductsGrid。从GridView的职能标记里,选择创建一个新的ObjectDat
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025银行考官面试题及答案
- 合水安全驾驶员培训课件
- 小学书法兴趣班教学活动方案
- 餐饮行业节约用水用电方案
- 矿业合作协议及生产责任分配方案
- 农田水利建设项目监理管理办法范例
- 医院安全培训计划方案课件
- 园林绿化工程施工技术手册范文
- 人力资源培训课程案例
- 参观游览的文章课件
- 资产评估机构采购方案投标文件(技术方案)
- 《老年上消化道出血急诊诊疗专家共识(2024)》解读
- 维修人员考核管理办法
- 2025-2030中国H发泡剂行业应用态势与需求规模预测报告
- 2025至2030中国氧化铝氧化锆磨料行业发展趋势分析与未来投资战略咨询研究报告
- 2025年新高考2卷(新课标Ⅱ)数学试卷(含答案及解析)
- 全屋定制合同赔付协议书
- 英语横向课题申报书
- 小学财务管理制度
- T-CNAS 04-2019 住院患者身体约束护理
- 道路危险品运输驾押人员培训
评论
0/150
提交评论