简体   繁体   中英

Removing items from a collection using WCF Data Services

I have a checked listbox in a Windows Forms app that allows the user to assign one or more security groups to a selected user.

Using WCF Data Services, I am able to populate the box without a problem. However, as the user changes selections in the box and tries to save those changes, I run into issues.

Here is the code, with comments to explain my logic.

private void ProcessSecurityGroupSelection_Original()
{
  //Get a reference to the selected user, including the associated SecurityGroups.
  var user = _ctx.Users
    .AddQueryOption("$filter", "UserID eq " + ((DataService.User)lstUsers.SelectedItem).UserID)
    .AddQueryOption("$expand", "SecurityGroups")
    .First();

  //Remove all the SecurityGroups so we can replace them.
  user.SecurityGroups.Clear();

  foreach (var selectedGroup in lstSecurityGroups.CheckedItems)
  {
    //Loop through the selected SecurityGroups, linking and adding each SecurityGroup to the User object.
    var securityGroup = (from sg in _ctx.SecurityGroups
                 where sg.SecurityGroupID == ((DataService.SecurityGroup)selectedGroup).SecurityGroupID
                 select sg).First();

    _ctx.AddLink(user, "SecurityGroups", securityGroup);

    user.SecurityGroups.Add(securityGroup);
  }

  _ctx.UpdateObject(user);
  _ctx.SaveChanges();
}

When the code hits the AddLink method for a SecurityGroup that had previously been selected, I get an error stating "The context is already tracking the relationship." It doesn't appear that the Clear() method removes any links in the context.

How do I go about removing the existing links, or am I approaching this all wrong?

I had the same issue with a silverlight project. I have taken the solution which worked for me and applied it to your User/SecurityGroup model.

Add the following to your user class:

public User()
    {
    this.SecurityGroups.CollectionChanged += (sender, e) =>
        {
        if (e.Action == Add)
            {
            foreach (SecurityGroup AddedGroup in e.NewItems)
            AddSecurityGroup(AddedGroup);
            }
        if (e.Action == Remove)
            {
            foreach (SecurityGroup RemovedGroup in e.OldItems)
            RemoveSecurityGroup(RemovedGroup);
            }

        };

    ..... rest of constructor
    }



public void AddSecurityGroup(SecurityGroup secGroup)
    {
    LinkDescriptor descriptr = _ctx.GetLinkDescriptor(this, "SecurityGroups", secGroup);

    if (descriptr == null)
        _ctx.AddLink(this, "SecurityGroups", secGroup);

    else if (descriptr.State == EntityStates.Deleted)
        _ctx.DetachLink(this, "SecurityGroups", secGroup);

    }


public void RemoveSecurityGroup (SecurityGroup secGroup)
    {
    LinkDescriptor descriptr = _ctx.GetLinkDescriptor(this, "SecurityGroups", secGroup);

    if (descriptr == null)
        {
        _ctx.AttachLink(this, "SecurityGroups", secGroup);
        _ctx.DeleteLink(this, "SecurityGroups", secGroup);
        }

    else if (descriptr.State == EntityStates.Added)
        _ctx.DetachLink(this, "SecurityGroups", secGroup);

    else
        _ctx.DeleteLink(this, "SecurityGroups", secGroup);

    }

Now remove the line:

_ctx.AddLink(user, "SecurityGroups", securityGroup);  

from your code above.

One way to remove the links is to add an event like the following:

private void lstSecurityGroups_ItemCheck(object sender, ItemCheckEventArgs e)
{
    if (e.NewValue == CheckState.Unchecked)
    {
        _ctx.DetachLink(user, "SecurityGroups", securityGroup);
    }
    else if (e.NewValue == CheckState.Checked)
    {
        _ctx.AddLink(user, "SecurityGroups", securityGroup);
    }
}

Note that DeleteLink will mark the entity for deletion also, which will cause an error if called multiple times. If you just want to remove the link, use DetachLink .

Based on A Aiston's answer I prepared a set of extensions to help with handling of Added then Deleted and Deleted then Added links in general.

  1. It's generic
  2. AddSecurityGroup shoud also attach link if it was previously deleted
  3. I prefer to avoid magic strings at some small performance cost

     public static class DataServiceContextExtensions { public static void AddOrAttachLink<TSource>(this DataServiceContext context, object source, Expression<Func<TSource>> sourceProperty, object target) { AddOrAttachLink(context, source, sourceProperty.GetExpressionMemberInfo().Name, target); } public static void AddOrAttachLink<TSource, TTarget>(this DataServiceContext context, TSource source, Expression<Func<TSource, ICollection<TTarget>>> sourceProperty, TTarget target) { AddOrAttachLink(context, source, sourceProperty.GetExpressionMemberInfo().Name, target); } public static void AddOrAttachLink(this DataServiceContext context, object source, string propertyName, object target) { var descriptor = context.GetLinkDescriptor(source, propertyName, target); if(descriptor == null) { context.AddLink(source, propertyName, target); } else if(descriptor.State == EntityStates.Deleted) { context.DetachLink(source, propertyName, target); context.AttachLink(source, propertyName, target); } } public static void DeleteOrDetachLink<TSource>(this DataServiceContext context, object source, Expression<Func<TSource>> sourceProperty, object target) { DeleteOrDetachLink(context, source, sourceProperty.GetExpressionMemberInfo().Name, target); } public static void DeleteOrDetachLink<TSource, TTarget>(this DataServiceContext context, TSource source, Expression<Func<TSource, ICollection<TTarget>>> sourceProperty, TTarget target) { DeleteOrDetachLink(context, source, sourceProperty.GetExpressionMemberInfo().Name, target); } public static void DeleteOrDetachLink(this DataServiceContext context, object source, string propertyName, object target) { var descriptor = context.GetLinkDescriptor(source, propertyName, target); if(descriptor == null) { context.AttachLink(source, propertyName, target); context.DeleteLink(source, propertyName, target); } else if(descriptor.State == EntityStates.Added) { context.DetachLink(source, propertyName, target); } else { context.DeleteLink(source, propertyName, target); } } public static MemberInfo GetExpressionMemberInfo(this Expression expression) { var lambda = (LambdaExpression)expression; MemberExpression memberExpression; if(lambda.Body is UnaryExpression) { var unaryExpression = (UnaryExpression)lambda.Body; memberExpression = (MemberExpression)unaryExpression.Operand; } else { memberExpression = (MemberExpression)lambda.Body; } return memberExpression.Member; } } 

Usage:

var ctx = new YourContext();
ctx.AddOrAttachLink(addTo, () => addTo.Collection, toAdd);
ctx.DeleteOrDetachLink(removeFrom, () => removeFrom.Collection, toRemove);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM