如何编写LINQ to SQL查询来更新标签?

时间:2011-08-13 18:04:50

标签: c# linq-to-sql tagging

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

我有以下表格:

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

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

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

此方法将执行以下操作:

  • 在标签表中尚未存在的currentTags中创建任何新标签。
  • 获取旧的ImageTag图片。
    • 删除currentTags中不再存在的所有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实现   除Contains运算符之外的运算符。

我是否可以更轻松地处理添加新标记,删除旧ImageTag,在LINQ to SQL中添加新ImageTag的操作?

2 个答案:

答案 0 :(得分:0)

似乎这样最容易

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是我最近使用的。这也取决于启用延迟加载。如果不是,则必须强制包含图像标记。

答案 1 :(得分:0)

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

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);