2012-07-07 26 views
0

我有以下几个方面,我可以让它按照我的意愿工作,但我认为我的方法是错误的,请你解释一下如何做到这一点以更有效的方式?同时也在Categories上循环并且在Insert()方法中与Districts相同。更好的实现INSERT而无需多次触击数据库的方法

在此先感谢。

#region Methods 
    public int Insert(List<District> Districts, List<Category> Categories) 
    { 
     StringBuilder sqlString = new StringBuilder("INSERT INTO Stores (name, image) VALUES (@Name, @Image);"); 

     using (SqlConnection sqlConnection = new 
      SqlConnection(ConfigurationManager.ConnectionStrings["OahuDB"].ConnectionString)) 
     { 
      SqlCommand sqlCommand = new SqlCommand(sqlString.ToString(), sqlConnection); 
      sqlCommand.Parameters.AddWithValue("@Name", this.Name); 
      sqlCommand.Parameters.AddWithValue("@Image", this.Image); 

      sqlConnection.Open(); 
      int x = (int)sqlCommand.ExecuteScalar(); 

      sqlString.Clear(); 
      sqlCommand.Parameters.Clear(); 

      foreach (District item in Districts) 
      { 
       sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (@DistrictID, @StoreID);"); 
       sqlCommand.CommandText = sqlString.ToString(); 
       sqlCommand.Parameters.AddWithValue("@DistrictID", item.ID); 
       sqlCommand.ExecuteNonQuery(); 
      } 

      return x; 
     } 
    } 

编辑

是错了实现上述通过执行以下操作?

  sqlString.Clear(); 
      sqlCommand.Parameters.Clear(); 
      sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (@DistrictID, @StoreID);"); 
      sqlCommand.CommandText = sqlString.ToString(); 
      sqlCommand.Parameters.AddWithValue("@StoreID", x); 
      foreach (District item in Districts) 
      { 
       sqlCommand.Parameters.AddWithValue("@DistrictID", item.ID); 
       sqlCommand.ExecuteNonQuery(); 
      } 
      sqlString.Clear(); 
      sqlCommand.Parameters.Clear(); 
      sqlString.AppendLine("INSERT INTO categories_has_stores (category_id, store_id) VALUES (@CategoryID, @StoreID);"); 
      sqlCommand.CommandText = sqlString.ToString(); 
      sqlCommand.Parameters.AddWithValue("@StoreID", x); 
      foreach (Category item in Categories) 
      { 
       sqlCommand.Parameters.AddWithValue("@CategoryID", item.ID); 
       sqlCommand.ExecuteNonQuery(); 
      } 
+0

请注意,我知道上面的方法中缺少一些东西,例如StoreID和循环继续附加INSERT语句的事实。 – user1027620 2012-07-07 16:01:17

+3

您可以例如创建一个包含所有''DistrictID'作为列表的单个XML,然后仅调用一次'INSERT'(并使用XQuery语句从XML结构中提取单个'ID'值) – 2012-07-07 16:04:46

回答

4

第一个明显的事情是给SqlCommand的不变部分迁出循环

sqlCommand.Parameters.Clear(); 
sqlString.Clear(); 
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (@DistrictID, @StoreID);"); 
sqlCommand.CommandText = sqlString.ToString(); 
sqlCommand.Parameters.AddWithValue("@DistrictID", 0); // as dummy value 
sqlCommand.Parameters.AddWithValue("@StoreID", x); // invariant 
foreach (District item in Districts) 
{ 
    sqlCommand.Parameters["@DistrictID"].Value = item.ID; 
    sqlCommand.ExecuteNonQuery(); 
} 

,但是这并没有回答你的根本问题。如何避免多次访问数据库。
你可以建立多个插入一个这样的查询

sqlString.Clear(); 
sqlString.Append("INSERT INTO districts_has_stores (district_id, store_id) VALUES ("); 
foreach(District item in Districts) 
{ 
    sqlString.Append(item.ID.ToString); 
    sqlString.Append(", ") 
    sqlString.Append(x.ToString()); 
    sqlString.Append("),"); 
} 
sqlString.Length--; 
sqlCommand.CommandText = sqlString.ToString() 

但字符串连接实在是一个不好的做法,我提出这个解决方案只是作为一个例子,我不想暗示这种做法。

最后一种可能性是Table-Valued Parameters(仅限于SqlServer 2008)。

首先你需要创建一个表中的SQL类型,你会通过在

CREATE TYPE dbo.DistrictsType AS TABLE 
    (DistrictID int, StoreID int) 

,并会从数据表中

CREATE PROCEDURE usp_InsertDistricts 
(@tvpNewDistricts dbo.DistrictsType READONLY) 
AS 
BEGIN 
    INSERT INTO dbo.Districts (DistrictID, StoreID) 
    SELECT dt.DistrictID, dt.StoreID FROM @tvpNewDistricts AS dt; 
END 

过去了,然后,回到插入数据的StoredProcedure您的代码将您的区域传入存储过程

(可能需要将您的列表转换为DataTable)

DataTable dtDistricts = ConvertListToDataTable(Districts); 
SqlCommand insertCommand = new SqlCommand("usp_InsertDistricts", sqlConnection); 
SqlParameter p1 = insertCommand.Parameters.AddWithValue("@tvpNewDistricts", dtDistricts); 
p1.SqlDbType = SqlDbType.Structured; 
p1.TypeName = "dbo.DistrictsType"; 
insertCommand.ExecuteNonQuery(); 

那么,如果你回头看看上面的链接,你会发现其他的方式来传递你的数据一步到数据库后端....(滚动到最后,你会发现一个方法不需要对数据库的存储过程)

+0

此行是什么? 'sqlCommand.Parameters.AddWithValue(“@ DistrictID”,0); //作为虚拟值,因为您已经在循环中添加了一个.Parameters [“”]。值 – user1027620 2012-07-07 16:13:32

+0

@ user1027620您不能设置不存在的参数的值,您需要先创建参数。有一个'.Add',但不需要虚拟值,这可能更好。 – hvd 2012-07-07 16:16:36

+1

@Steve您能否更具体地说明为什么串接是一种不好的做法?在这个特定的情况下,我没有看到真正的缺点 - 连接不是基于用户输入,所以没有SQL注入的可能性。 – 2012-07-07 16:56:52

0

就个人而言,我会创建一个存储过程的插入和传递表值PARAM,这样可以让你做

INSERT tbl (f1, f2, ... fN) 
SELECT * FROM @TVP 

http://msdn.microsoft.com/en-us/library/bb510489.aspx

除非您使用的是SQL 2005,否则我会在存储过程中使用XML参数并序列化要插入的集合。

2

假设存储有标识列,在SQL Server中,创建一个表类型和表值参数,以利用它的优势:

CREATE TYPE dbo.DistrictsTVP AS TABLE 
(
    DistrictID INT -- PRIMARY KEY? I hope so. 
); 
GO 

CREATE PROCEDURE dbo.InsertStoreAndDistricts 
    @Name NVARCHAR(255), 
    @Image <some data type???>, 
    @Districts dbo.DistrictsTVP READONLY 
AS 
BEGIN 
    SET NOCOUNT ON; 

    DECLARE @StoreID INT; 

    INSERT dbo.Stores(name, [image]) SELECT @Name, @Image; 

    SET @StoreID = SCOPE_IDENTITY(); 

    INSERT dbo.district_has_stores(district_id, store_id) 
    SELECT DistrictID, @StoreID 
     FROM @Districts; 
END 
GO 

然后在C#中,你可以通过你的列表中的情况下直接任何循环:

using (...) 
    { 
    SqlCommand cmd  = new SqlCommand("dbo.InsertStoreAndDistricts", sqlConnection); 
    cmd.CommandType  = CommandType.StoredProcedure; 
    SqlParameter tvparam = cmd.Parameters.AddWithValue("@Districts", Districts); 
    tvparam.SqlDbType    = SqlDbType.Structured; 

    // other params here - name and image 

    cmd.ExecuteNonQuery(); 
    } 
+0

是否有另一种方法可以在没有存储过程的情况下执行此操作?谢谢 – user1027620 2012-07-07 16:33:31

+0

@ user1027620你能解释一下为什么吗? – 2012-07-07 16:33:55

+0

我真的不熟悉存储过程或他们真正的工作。这需要一些时间来了解您在上面分享的内容。我确信使用存储过程可能会带来好处,但我不会这样做。 – user1027620 2012-07-07 16:36:42

1

最近在我的项目,我用XML作为我的存储过程数据类型并没有插入更新和删除只需进行一次拍摄,而不是多次进入数据库的。

样品存储过程

ALTER PROCEDURE [dbo].[insertStore] 
@XMLDATA xml, 
@name varchar(50), 
@image datatype 
AS 
Begin 
    INSERT INTO Store 
    (name 
    ,image 
) 
Select XMLDATA.item.value('@name[1]', 'varchar(10)') AS Name, 
XMLDATA.item.value('@image[1]', 'yourData type') AS Image 
FROM @XMLDATA.nodes('//Stores/InsertList/Store') AS XMLDATA(item) 
END 

同样你可以写更新和删除。在C#u需要创建XML

public string GenerateXML(List<District> Districts) 
var xml = new StringBuilder(); 
var insertxml = new StringBuilder(); 
xml.Append("<Stores>"); 
for (var i = 0; i < Districts.Count; i++) 
     { var obj = Districts[i]; 
      insertxml.Append("<Store"); 
      insertxml.Append(" Name=\"" + obj.Name + "\" "); 
      insertxml.Append(" Image=\"" + obj.Image + "\" "); 
      insertxml.Append(" />"); 
     } 
xml.Append("<InsertList>"); 
xml.Append(insertxml.ToString()); 
xml.Append("</InsertList>"); 

SqlCommand cmd= new SqlCommand("insertStore",connectionString); 
cmd.CommandType=CommandType.StoredProcedure; 
SqlParameter param = new SqlParameter(); 
param.ParameterName ="@XMLData"; 
param.value=xml; 
paramter.Add(param); 
cmd.ExecuteNonQuery(); 
+1

有人可以解释我为downvote? – praveen 2012-07-07 16:36:15

+0

嗯,这不是我所以我不知道:/ – user1027620 2012-07-07 16:37:19

+2

我同意,我不确定,虽然我发现TVP比XML更容易使用。我还发现有人会提出一个答案,说“使用TVP,这是一个链接”,但不赞成一个回答“使用TVP”,并实际上显示了一些代码来演示在特定场景中如何完成。 StackOverflow的用户行为相当令人费解。 – 2012-07-07 16:37:22

0

想想你的系统设计。您需要插入的数据来自哪里?如果它已经存在于数据库,或其他数据库或其他类型的数据存储中,则应该能够实现更大容量的传输,只需在存储过程的循环中从一个数据库插入另一个数据库即可。

如果数据来自用户或某些不兼容的数据存储区,比如说从某个第三方程序导出数据,那么您基本上必须意识到将数据导入数据库将涉及很多次往返到数据库。您可以使用一些表格或XML等,但实际上它们更接近使用其他方法进行批量插入。

底线是SQL数据库被设计为一次插入一个。这是99%的时间,因为你永远不会要求使用用户界面的用户一次输入数千件东西。

+0

“一次插入”和使用完全独立的命令以及所有需要的脚手架之间有区别。我质疑你的99%的数字。许多应用程序允许您从下拉列表中选择多个项目,或者选中多个复选框。我敢打赌,99%的这些*不会将每个复选框的值作为完全独立的命令提交。 – 2012-07-07 18:25:32

相关问题