LINQ查询匹配两个项目列表

时间:2012-10-07 09:45:43

标签: c# asp.net linq entity-framework

我的问题是我有一个来自实体框架的'属性'属性。 所以我检索这个有一个属性标签列表的对象,可以通过attribute.AttributeTags访问它们。现在我有asp:TextBox用户可以编辑,删除和添加新标签(以逗号分隔)。 (在页面加载时,我将属性标记添加到此TextBox

在页面上回发后,我返回用户输入并将其拆分为字符串数组并将其存储在名为AttributeTags的变量中。

现在,我想添加来自EF的原始attributes列表中未包含的新标记,并希望remove包含attributes但不包含的标记。在用户输入字符串数组AttributeTags中找到。

我正在做这样的事情:

        BusinessObjects.Attribute attribute = db.Attributes.FirstOrDefault(a => a.attribute_id == AttributeID);
        string[] AttributeTags = txtAttributeTags.Text.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
        foreach (var item in AttributeTags)
        {
            if (!attribute.AttributeTags.Any(t => t.value == item))
            {
                AttributeTag tag = new AttributeTag { value = item, timestamp = DateTime.Now };
                attribute.AttributeTags.Add(tag);
            }
            else
            {
                AttributeTag tag = attribute.AttributeTags.FirstOrDefault(t => t.value == item);
            }
        }

但我有点困在这里因为我对LINQ和EF很新。

6 个答案:

答案 0 :(得分:2)

我有两种解决方案。


第一个解决方案

我们可以创建一个ExcepWith方法,允许我们移除ICollection<T>中已经提供IEnumerable<T>的所有项目。这种方法的代码如下:

public static int ExceptWith<TItem>
(
    this ICollection<TItem> collection,
    IEnumerable<TItem> other
)
{
    if (ReferenceEquals(collection, null))
    {
        throw new ArgumentNullException("collection");
    }
    else if (ReferenceEquals(other, null))
    {
        throw new ArgumentNullException("other");
    }
    else
    {
        int count = 0;
        foreach (var item in other)
        {
            while (collection.Remove(item))
            {
                count++;
            }
        }
        return count;
    }
}

现在您有一个string[]的用户输入,该数组是IEnumerable<string>但不是ICollection<string> ...可以轻松解决如下:

而不是:

string[] AttributeTags =
    txtAttributeTags.Text.Split
    (
        new string[] { "," },
        StringSplitOptions.RemoveEmptyEntries
    );

你这样做:

var AttributeTags =
    new List<string>
    (
        txtAttributeTags.Text.Split
        (
            new string[] { "," },
            StringSplitOptions.RemoveEmptyEntries
        )
    );

甚至这个:

var AttributeTags =
    txtAttributeTags.Text.Split
    (
        new string[] { "," },
        StringSplitOptions.RemoveEmptyEntries
    ).ToList();

现在你可以这样做:

AttriuteTags.ExceptWith(existingTags);

由于attribute.AttributeTag的类型不是IEnumerable<string>,您使用Select:

AttriuteTags.ExceptWith(attribute.AttributeTag.Select(item => item.value));

只留下列表中的新标签。


注意:这个方法取决于Remove的实现,如果你需要做一个特殊的比较,那么你对这个方法运气不好。


第二种解决方案

还有另一种方式。您可以使用Enumerable类中的Except

string[] AttributeTags =
    txtAttributeTags.Text.Split
    (
        new string[] { "," },
        StringSplitOptions.RemoveEmptyEntries
    );
var newTags = AttributeTags.Except(existingTags);

由于attribute.AttributeTag的类型不是IEnumerable<string>,您使用Select:

string[] AttributeTags =
    txtAttributeTags.Text.Split
    (
        new string[] { "," },
        StringSplitOptions.RemoveEmptyEntries
    );
var newTags = AttributeTags.Except
(
    attribute.AttributeTag.Select(item => item.value)
);

这就是新标签,新标签。


注意:如果您需要进行特殊比较,那么您应该使用the other overload of the method

string[] AttributeTags =
    txtAttributeTags.Text.Split
    (
        new string[] { "," },
        StringSplitOptions.RemoveEmptyEntries
    );
var newTags = AttributeTags.Except(attribute.AttributeTag, equalityComparer);

遗憾的是,equalityComparer是实现IEqualityComparer的类的对象,这意味着你不能在那里使用lambdas。为此你可以添加这个类:

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _comparison;
    private Func<T, int> _getHashCode;

    public CustomEqualityComparer
    (
        Func<T, T, bool> comparison,
        Func<T, int> getHashCode
    )
    {
        if (ReferenceEquals(comparison, null))
        {
            throw new ArgumentNullException("comparison");
        }
        else if (ReferenceEquals(getHashCode, null))
        {
            throw new ArgumentNullException("getHashCode");
        }
        else
        {
           _comparison = comparison;
           _getHashCode = getHashCode;
        }
    }

    public bool Equals(T x, T y)
    {
        return _comparison.Invoke(x, y);
    }

    public int GetHashCode(T obj)
    {
        return _getHashCode.Invoke(obj);
    }
}

现在调用(例如):

string[] AttributeTags =
    txtAttributeTags.Text.Split
    (
        new string[] { "," },
        StringSplitOptions.RemoveEmptyEntries
    );
var newTags = AttributeTags.Except
(
    existingTags,
    new CustomEqualityComparer<string>
    (
        (a, b) => 1, //your custom comparison here
        str => str.GetHashCode()
    )
);

由于attribute.AttributeTag的类型不是IEnumerable<string>,您使用Select:

string[] AttributeTags =
    txtAttributeTags.Text.Split
    (
        new string[] { "," },
        StringSplitOptions.RemoveEmptyEntries
    );
var newTags = AttributeTags.Except
(
    attribute.AttributeTag.Select(item => item.value),
    new CustomEqualityComparer<string>
    (
        (a, b) => 1, //your custom comparison here
        str => str.GetHashCode()
    )
);

添加新标签

现在您已经拥有了新标签,我们可以在newTags中对其进行迭代以添加新标签:

var now = DateTime.Now;
foreach (var item in newTags)
{
    AttributeTag tag = new AttributeTag { value = item, timestamp = now };
    attribute.AttributeTags.Add(tag);
}

比较解决方案

这些方法的区别是什么?

  • 第一个需要更少的内存
  • 第一个需要定义一个新方法。
  • 第一个不允许自定义IEqualityComparer<T>
  • 第二个允许延迟执行。
  • 第二个使用(不需要)助手类。

答案 1 :(得分:2)

如何做你想做的事情的简单例子。

var fromDB = new List<string>() { "a", "b", "c", "d", "e" };
var userInput = new List<string>() { "c", "d", "e", "f", "g" };
var result = fromDB.Join(userInput, x => x, y => y, (x, y) => x).Union(userInput);

现在您所要做的就是用结果替换数据库内容。

答案 2 :(得分:1)

使用Iesi.Collections可以非常优雅地解决这个问题 它有几个实现,这里有一个:Set Collections

ListSet set1 = new ListSet(new [] {"1","2","8"});
ListSet set2 = new ListSet(new [] {"8","16","32"});
var union = set1 | set2;        // "1","2","8","16","32"
var intersect = set1 & set2;    // "8"
var diff = set1 ^ set2;         // "1","2","16","32"
var minus = set1 - set2;        // "1","2"

答案 3 :(得分:1)

这是我测试过的代码。在实体框架中有很多节省方法。

注意:请确保在迭代集合时不要修改/删除项目。

enter image description here

<asp:TextBox ID="txtAttributeTags" runat="server" />
<asp:Button runat="server" ID="SubmitButton" OnClick="SubmitButton_Click" 
  Text="Submit" />

public const int AttributeID = 1;

protected void Page_Load(object sender, EventArgs e)
{
  if (!IsPostBack)
  {
    using (var db = new AttributeEntities())
    {
      var tags = db.AttributeTags
        .Where(a => a.attribute_id == AttributeID)
        .Select(a => a.value);

      txtAttributeTags.Text = string.Join(",", tags);
    }
  }
}

protected void SubmitButton_Click(object sender, EventArgs e)
{
  using (var db = new AttributeEntities())
  {
    string[] newTags = txtAttributeTags.Text.Split(new[] {","}, 
      StringSplitOptions.RemoveEmptyEntries);

    var oldTags = db.AttributeTags.Where(t => t.attribute_id == AttributeID);

    foreach (var tag in oldTags.Where(o => !newTags.Contains(o.value)))
        db.AttributeTags.DeleteObject(tag);

    foreach (var tag in newTags.Where(n => !oldTags.Any(o => o.value == n)))
      db.AttributeTags.AddObject(new AttributeTag
      {
          attribute_id = AttributeID, value = tag, timestamp = DateTime.Now
      });

    db.SaveChanges();
    }
  }
}

答案 4 :(得分:0)

我们在db对象的Attributes属性上使用Remove方法然后保存更改

 db.Attributes.Remove( object );

然后将更改保存到db对象。

如果我正确地假设您的db对象是EF中的连接对象,那么这应该有用。

答案 5 :(得分:0)

我不能做一个完整的测试,但这些方面应该做的事情:

BusinessObjects.Attribute attribute = db.Attributes.FirstOrDefault(a => a.attribute_id == AttributeID);
string[] AttributeTags = txtAttributeTags.Text.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);

foreach (var item in from a in AttributeTags
                     where attribute.AttributeTags.Any(t => t.value == a)
                     select new AttributeTag 
                     { 
                         value = item, 
                         timestamp = DateTime.Now 
                     })
    attribute.AttributeTags.Add(item);

foreach (var item in from a in attribute.AttributeTags
                     where AttributeTags.Any(t => t == a.value)
                     select a)
    attribute.AttributeTags.Remove(item);

db.SaveChanges();