【问题标题】:How can I write a LINQ to SQL query to update tags?如何编写 LINQ to SQL 查询来更新标签?
【发布时间】:2011-08-13 18:04:49
【问题描述】:

我有一个图片网站,用户可以在其中标记照片,就像您可以在 Stackoverflow 上标记问题一样。

我有以下表格:

Images [ID, URL, etc]   
Tags  [ID, TagName]    
ImageTag  [TagID, ImageID]

我想写一个带有签名的方法:

public void UpdateImageTags(int imageId, IEnumerable<string> currentTags)

此方法将执行以下操作:

  • 在当前标签中创建标签表中尚不存在的任何新标签。
  • 获取图像的旧 ImageTag。
    • 删除所有不再存在于 currentTag 中的 ImageTag
    • 在 currentTags 和 oldTags 之间添加任何新的 ImageTag。

这是我对该方法的尝试:

public void UpdateImageTags(int imageId, IEnumerable<string> currentTags)
{
    using (var db = new ImagesDataContext())
    {
        var oldTags = db.ImageTags.Where(it => it.ImageId == imageId).Select(it => it.Tag.TagName);
        var added = currentTags.Except(oldTags);
        var removed = oldTags.Except(currentTags);

        // Add any new tags that need created
        foreach (var tag in added)
        {
            if (!db.Tags.Any(t => t.TagName == tag))
            {
                db.Tags.InsertOnSubmit(new Tag { TagName = tag });
            }
        }               
        db.SubmitChanges();

        // Delete any ImageTags that need deleted.
        var deletedImageTags = db.ImageTags.Where(it => removed.Contains(it.Tag.TagName));
        db.ImageTags.DeleteAllOnSubmit(deletedImageTags);

        // Add any ImageTags that need added.
        var addedImageTags = db.Tags.Where(t => added.Contains(t.TagName)).Select(t => new ImageTag { ImageId = imageId, TagId = t.TagId });
        db.ImageTags.InsertAllOnSubmit(addedImageTags);
        db.SubmitChanges();
    }
}

但是,这行失败:

db.ImageTags.DeleteAllOnSubmit(deletedImageTags);

出现错误:

在查询的 LINQ to SQL 实现中不能使用本地序列 除包含运算符之外的运算符。

有没有更简单的方法可以处理在 LINQ to SQL 中添加新标签、删除旧 ImageTags、添加新 ImageTags 的操作?

【问题讨论】:

  • 数据库中有多少个标签?这将是“从数据库中读取所有标签并进行比较”与“每个项目的查询”之间的关键因素
  • @Marc Gravell,可能会有数千个标签。也许超过 10k,少于 50k?总猜测。它仍在开发中,我们已经有将近一千个了。
  • 那不能在服务器的TSQL中做吗?
  • @Mac Gravell,是的,它可以。 linq to sql 是否有一些限制会阻止它有效地执行此操作?
  • @Viktor 有限制吗?水合所有这些对象、进行更改并将它们推回的开销明显高于在数据库级别发出单个更新语句而无需通过网络传递所有这些数据。将其放入存储过程中,如果需要,可以使用 LINQ 调用该过程。

标签: c# linq-to-sql tagging


【解决方案1】:

这似乎是最简单的

public void UpdateImageTags(int imageId, IEnumerable<string> currentTags) 
{ 
    using (var db = new ImagesDataContext()) 
    { 
        var image = db.Images.Where(it => it.ImageId == imageId).First()
        image.Tags.Clear();
        foreach(string s in currentTags)
        {
            image.Tags.Add(new Tag() { TagName = s});
        } 
        db.SubmitChanges();  
    }
 }

这可能需要对 LinqtoSQL 稍作修改。 EF是我最近使用的。这也取决于启用延迟加载。如果不是,您将不得不强制包含图像标签。

【讨论】:

    【解决方案2】:

    这是处理多对多关系的辅助方法:

    public static void UpdateReferences<FK, FKV>(
        this EntitySet<FK> refs,
        Expression<Func<FK, FKV>> fkexpr,
        IEnumerable<FKV> values)
      where FK : class
      where FKV : class
    {
      Func<FK, FKV> fkvalue = fkexpr.Compile();
      var fkmaker = MakeMaker(fkexpr);
      var fkdelete = MakeDeleter(fkexpr);
    
      var fks = refs.Select(fkvalue).ToList();
      var added = values.Except(fks);
      var removed = fks.Except(values);
    
      foreach (var add in added)
      {
        refs.Add(fkmaker(add));
      }
    
      foreach (var r in removed)
      {
        var res = refs.Single(x => fkvalue(x) == r);
        refs.Remove(res);
        fkdelete(res);
      }
    }
    
    static Func<FKV, FK> MakeMaker<FKV, FK>(Expression<Func<FK, FKV>> fkexpr)
    {
      var me = fkexpr.Body as MemberExpression;
    
      var par = Expression.Parameter(typeof(FKV), "fkv");
      var maker = Expression.Lambda(
          Expression.MemberInit(Expression.New(typeof(FK)),
            Expression.Bind(me.Member, par)), par);
    
      var cmaker = maker.Compile() as Func<FKV, FK>;
      return cmaker;
    }
    
    static Action<FK> MakeDeleter<FK, FKV>(Expression<Func<FK, FKV>> fkexpr)
    {
      var me = fkexpr.Body as MemberExpression;
      var pi = me.Member as PropertyInfo;
    
      var assoc = Attribute.GetCustomAttribute(pi, typeof(AssociationAttribute))
        as AssociationAttribute;
    
      if (assoc == null || !assoc.DeleteOnNull)
      {
        throw new ArgumentException("DeleteOnNull must be set to true");
      }
    
      var par = Expression.Parameter(typeof(FK), "fk");
      var maker = Expression.Lambda(
          Expression.Call(par, pi.GetSetMethod(),
            Expression.Convert(Expression.Constant(null), typeof(FKV))), par);
    
      var cmaker = maker.Compile() as Action<FK>;
      return cmaker;
    }
    

    用法:

    IEnumerable<Tag> values = ...;
    Image e = ...;
    e.ImageTags.UpdateReferences(x => x.Tag, tags);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多