推荐的方法插入许多行与城堡ActiveRecord和忽略任何副本

本文关键字:ActiveRecord 副本 任何 城堡 方法 插入 许多行 | 更新日期: 2023-09-27 18:15:17

我有一个webmethod,它将一堆食谱插入数据库中的队列中(用于存储用户对烹饪感兴趣的食谱,类似于NetFlix的电影队列)。用户可以一次勾选一堆食谱并将它们排队。我有类似这样的代码:

[WebMethod]
public void EnqueueRecipes(SecurityCredentials credentials, Guid[] recipeIds)
{
    DB.User user = new DB.User(credentials);
    using (new TransactionScope(OnDispose.Commit))
    {
       foreach (Guid rid in recipeIds)
       {
          DB.QueuedRecipe qr = new DB.QueuedRecipe(Guid.NewGuid(), user, new DB.Recipe(rid));
          qr.Create();
       }
    }
}

我对UserId/RecipeId有一个唯一的约束,所以用户只能排队一次食谱。然而,如果他们碰巧选择了一个已经在他们队列中的食谱,我真的不想用错误消息打扰用户,我只想忽略那个食谱。

如果违反了惟一约束,上面的代码将抛出一个SQL异常。解决这个问题的最好方法是什么?忽略重复的行。我现在的想法是:
  • 1)首先从数据库中加载用户的整个队列并检查先看那张单子。如果配方已经存在,只需加入continuefor循环。优点:没有不必要的SQL插入被发送到数据库。缺点:较慢,特别是当用户有一个大队列时。
  • 2)不要使用ActiveRecord,而是传递整个recipeIds数组转换成SQL函数。这个函数将检查每一行是否存在第一。优点:潜在的快速,让SQL处理所有的脏工作。缺点:打破了ActiveRecord模式,需要新的DB代码,这是通常更难维护,实施成本更高。
  • 3)每次循环后CreateAndFlush。基本上,不要运行整个过程在单个事务中循环。按添加的方式提交每一行捕获SQL错误并忽略。优点:低启动成本需要新的SQL后端代码。缺点:插入可能会变慢一次向数据库中输入大量行,尽管这对用户来说很可疑我从来不会一次提交十几个新食谱。

关于Castle和NHibernate框架还有什么其他的小技巧吗?另外,我的SQL后端是PostgreSQL 9.0。谢谢!

更新:

我尝试了第一种方法,它似乎工作得很好。我突然想到,我不需要加载整个队列,只需要加载出现在recipeIds中的队列。我相信我的foreach()循环现在是O(n^2),这取决于List<Guid>::Contains()的效率,但我认为这对于我将要使用的尺寸来说可能是不错的。

//Check for dupes
DB.QueuedRecipe[] dbRecipes = DB.QueuedRecipe.FindAll(Expression.In("Recipe",
   (from r in recipeIds select new DB.Recipe(r)).ToArray()
));
List<Guid> existing = (from r in dbRecipes select r.Recipe.RecipeId).ToList();
using (new TransactionScope(OnDispose.Commit))
{
   foreach (Guid rid in recipeIds)
   {
      if (existing.Contains(rid))
         continue;
      DB.QueuedRecipe qr = new DB.QueuedRecipe(Guid.NewGuid(), user, new DB.Recipe(rid));
      qr.Create();
   }
}

推荐的方法插入许多行与城堡ActiveRecord和忽略任何副本

您可以使用单个SQL语句:

INSERT INTO user_recipe
SELECT new_UserId, new_RecipeId
FROM   user_recipe
WHERE  NOT EXISTS (
   SELECT *
   FROM   user_recipe
   WHERE  (UserId, RecipeId) = (new_UserId, new_RecipeId)
   );

SELECT只在该行不存在的情况下返回该行,所以它只会在这种情况下被插入。


批量插入的解决方案

如果你有一个很长的食谱列表要插入一次,你可以:

CREATE TEMP TABLE i(userId int, recipeid int) ON COMMIT DROP;
INSERT INTO i VALUES
(1,2), (2,4), (2,4), (2,7), (2,43), (23,113), (223,133);
INSERT INTO user_recipe
SELECT DISTINCT i.*  -- remove dupes from the insert candidates themselves
FROM   i
LEFT   JOIN user_recipe u USING (userid, recipeid)
WHERE  u.userid IS NULL;

一次插入一把的解决方案

临时表对于一些记录来说是多余的,正如Mike所评论的。

INSERT INTO user_recipe
SELECT i.* 
FROM  (
    SELECT DISTINCT *     -- only if you need to remove possible dupes
    FROM (
       VALUES (1::int, 2::int)
          ,(2, 3)
          ,(2, 4)
          ,(2, 4)            -- dupe will be removed
          ,(2, 43)
          ,(23, 113)
          ,(223, 133)
       ) i(userid, recipeid)
    ) i
LEFT   JOIN user_recipe u USING (userid, recipeid)
WHERE  u.userid IS NULL;