LINQ-to-SQL has been a PITA for me. We're using it to communicate to the database, and then send entities via WCF to a Silverlight app. Everything was working fine, until it came time to start editing (CUD) the entities, and their related data.
I was finally able to devise two for loops that allowed the CUD. I tried to refactor them, and I was so close, until I learned that I can't always do Lambda with L2S.
public static void CudOperation<T>(this DataContext ctx, IEnumerable<T> oldCollection, IEnumerable<T> newCollection, Func<T, T, bool> predicate)
where T : class
{
foreach (var old in oldCollection)
{
if (!newCollection.Any(o => predicate(old, o)))
{
ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(o => predicate(old, o)));
}
}
foreach (var newItem in newCollection)
{
var existingItem = oldCollection.SingleOrDefault(o => predicate(o, newItem));
if (existingItem != null)
{
ctx.GetTable<T>().Attach(newItem, existingItem);
}
else
{
ctx.GetTable<T>().InsertOnSubmit(newItem);
}
}
}
Called by:
ctx.CudOperation<MyEntity>(myVar.MyEntities, newHeader.MyEntities,
(x, y) => x.PkID == y.PkID && x.Fk1ID == y.Fk1ID && x.Fk2ID == y.FK2ID);
This almost worked. However, my Func needs to be an Expression>, and that's where I'm stuck.
Is there anyone who can tell me if this is possible? We have to be in .NET 3.5 due to SharePoint 2010.
Just change the parameter from:
Func<T, T, bool> predicate
To:
Expression<Func<T, T, bool>> predicate
The Expression is generated by the compiler.
Now, the issue is how to use this.
In your case, you need both a Func
and an Expression
, since you're using it in Enumerable
LINQ queries (func based) as well as the SQL LINQ queries (expression based).
In:
.Where(o => predicate(old, o))
The old
parameter is fixed. So we could change the parameter to:
Func<T, Expression<Func<T, bool>>> predicate
This means we can supply one argument (the 'fixed' one) and get back an expression.
foreach (var old in oldCollection)
{
var condition = predicate(old);
// ...
{
ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(condition));
}
}
We also need to use this in Any
. To get a Func from an Expression we can call Compile()
:
foreach (var old in oldCollection)
{
var condition = predicate(old);
if (!newCollection.Any(condition.Compile()))
{
ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(condition));
}
}
You can do the same thing with the next part.
There are two issues:
Compile()
lots. I'm not sure how much effect it would actually have, but I'd profile it to check.(x,y) => ...
you will be passing x => y => ...
. I'm not sure if this is a big deal for you.There might be a better way to do this :)
Here's an alternate method, which should be a bit faster, since the Expression only has to be compiled once. Create a rewriter that will 'apply' one argument, like this:
class PartialApplier : ExpressionVisitor
{
private readonly ConstantExpression value;
private readonly ParameterExpression replace;
private PartialApplier(ParameterExpression replace, object value)
{
this.replace = replace;
this.value = Expression.Constant(value, value.GetType());
}
public override Expression Visit(Expression node)
{
var parameter = node as ParameterExpression;
if (parameter != null && parameter.Equals(replace))
{
return value;
}
else return base.Visit(node);
}
public static Expression<Func<T2,TResult>> PartialApply<T,T2,TResult>(Expression<Func<T,T2,TResult>> expression, T value)
{
var result = new PartialApplier(expression.Parameters.First(), value).Visit(expression.Body);
return (Expression<Func<T2,TResult>>)Expression.Lambda(result, expression.Parameters.Skip(1));
}
}
Then use it like this:
public static void CudOperation<T>(this DataContext ctx,
IEnumerable<T> oldCollection,
IEnumerable<T> newCollection,
Expression<Func<T, T, bool>> predicate)
where T : class
{
var compiled = predicate.Compile();
foreach (var old in oldCollection)
{
if (!newCollection.Any(o => compiled(o, old)))
{
var applied = PartialApplier.PartialApply(predicate, old);
ctx.GetTable<T>().DeleteAllOnSubmit(ctx.GetTable<T>().Where(applied));
}
}
foreach (var newItem in newCollection)
{
var existingItem = oldCollection.SingleOrDefault(o => compiled(o, newItem));
if (existingItem != null)
{
ctx.GetTable<T>().Attach(newItem, existingItem);
}
else
{
ctx.GetTable<T>().InsertOnSubmit(newItem);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With