将列表<>传递给SQL存储过程

我经常不得不将多个项目加载到数据库中的特定logging。 例如:网页显示单个报表所包含的项目,所有项目都是数据库中的logging(报表是报表中的logging,项目是项目表中的logging)。 用户通过networking应用程序select要包括在单个报告中的项目,并且假设他们select3个项目并提交。 该过程将通过将logging添加到名为ReportItems(ReportId,ItemId)的表中,将这3个项目添加到此报告中。

目前,我会在代码中做这样的事情:

public void AddItemsToReport(string connStr, int Id, List<int> itemList) { Database db = DatabaseFactory.CreateDatabase(connStr); string sqlCommand = "AddItemsToReport" DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand); string items = ""; foreach (int i in itemList) items += string.Format("{0}~", i); if (items.Length > 0) items = items.Substring(0, items.Length - 1); // Add parameters db.AddInParameter(dbCommand, "ReportId", DbType.Int32, Id); db.AddInParameter(dbCommand, "Items", DbType.String, perms); db.ExecuteNonQuery(dbCommand); } 

这在存储过程中:

 INSERT INTO ReportItem (ReportId,ItemId) SELECT @ReportId, Id FROM fn_GetIntTableFromList(@Items,'~') 

函数返回一个整数列表。

我的问题是:有没有更好的方法来处理这样的事情? 请注意,我不是在询问数据库正常化或类似的事情,我的问题特别与代码有关。

如果要去SQL Server 2008是一个选项,有一个新的function称为“表值参数”来解决这个确切的问题。

在这里和这里查看TVP的更多详细信息,或者直接向Google询问“SQL Server 2008表值参数” – 您将find大量信息和示例。

强烈build议 – 如果你可以迁移到SQL Server 2008 …

你的string连接逻辑可能会被简化:

 string items = string.Join("~", itemList.Select(item=>item.ToString()).ToArray()); 

这将为您节省一些string连接,这在.Net中很昂贵。

我不认为你保存物品的方式有什么问题。 你正在限制前往数据库,这是一件好事。 如果你的数据结构比整数列表更复杂,我会build议XML。

注:我在评论中被问到这是否会节省我们的任何string连接(它确实是indeeed)。 我认为这是一个很好的问题,并希望跟进。

如果剥离开放的string。使用Reflectorjoin,您将看到Microsoft正在使用一些不安全的(以.Net的forms)技术,包括使用char指针和名为UnSafeCharBuffer的结构。 他们在做什么,当你真正把它煮沸时,是用指针来穿过一个空的string并build立连接。 请记住在.Net中string串联的主要原因是如此昂贵,因为string是不可变的,所以在每个串联中将一个新的string对象放在堆上。 这些内存操作是昂贵的。 String.Join(..)基本上是分配内存一次,然后用指针进行操作。 非常快。

你的技术的一个潜在的问题是,它不处理非常大的列表 – 你可能会超过你的数据库的最大string长度。 我使用一个帮助器方法,将整数值连接到一个string枚举中,每个string小于指定的最大值(以下实现也可以select检查并删除重复的id):

 public static IEnumerable<string> ConcatenateValues(IEnumerable<int> values, string separator, int maxLength, bool skipDuplicates) { IDictionary<int, string> valueDictionary = null; StringBuilder sb = new StringBuilder(); if (skipDuplicates) { valueDictionary = new Dictionary<int, string>(); } foreach (int value in values) { if (skipDuplicates) { if (valueDictionary.ContainsKey(value)) continue; valueDictionary.Add(value, ""); } string s = value.ToString(CultureInfo.InvariantCulture); if ((sb.Length + separator.Length + s.Length) > maxLength) { // Max length reached, yield the result and start again if (sb.Length > 0) yield return sb.ToString(); sb.Length = 0; } if (sb.Length > 0) sb.Append(separator); sb.Append(s); } // Yield whatever's left over if (sb.Length > 0) yield return sb.ToString(); } 

然后你使用它像这样:

 using(SqlCommand command = ...) { command.Connection = ...; command.Transaction = ...; // if in a transaction SqlParameter parameter = command.Parameters.Add("@Items", ...); foreach(string itemList in ConcatenateValues(values, "~", 8000, false)) { parameter.Value = itemList; command.ExecuteNonQuery(); } } 

为什么不使用表值参数? http://msdn.microsoft.com/en-us/library/bb675163.aspx

你要么做你已经得到的,传入一个分隔的string,然后parsing出一个表值,或者另外一个select是传递一个XML的奇妙和相同:

http://weblogs.asp.net/jgalloway/archive/2007/02/16/passing-lists-to-sql-server-2005-with-xml-parameters.aspx

我没有机会看看SQL 2008,但看看他们是否已经添加了任何新的function来处理这种types的事情。

下面是对sqlteam.com 中表值参数的一个非常明确的解释: 表值参数