Reputation: 1110
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?
Upvotes: 1
Views: 1609
Reputation: 3167
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.
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);
Upvotes: 1
Reputation: 717
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.
Upvotes: 2
Reputation: 4336
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
.
Upvotes: 1