I found a question that might be related, but since there's no stack trace I'm not sure.
However, I've got this exception: "System.InvalidOperationException: Sequence contains no matching element"
This is the LINQ query:
this.ItemVersionsUntyped.Set(x => x.TimeStampFromEditVersion, (DateTime)value).Update();
The relevant top of the stack trace:
System.Linq.Enumerable.Single(IEnumerable`1 source, Func`2 predicate) +2715797
Xtensive.Orm.BulkOperations.Operation`1..ctor(QueryProvider queryProvider) +525
Xtensive.Orm.BulkOperations.BulkUpdateOperation`1..ctor(IUpdatable`1 query) +183
Xtensive.Orm.BulkOperations.BulkExtensions.Update(IUpdatable`1 query) +51
ItemVersionsUntyped is just a plain EntitySet (inherited though, for some validation on adding, but nothing else is different.)
However, ItemVersionsUntyped is EntitySet<interface> (or IQueryable<baseclass>, I've tried both with the same error.) They are not HierarchyRoot.
I think the culprit is in Xtensive.Orm.BulkOperations.Operation - neither the interface not the base class are in the hierarchy. I think the problem is that it's tied down to exactly one type when there could be many matching types?
I've done some modifications just for fun (it's hardly tested at all, at least it executes without errors anymore) to the BulkUpdateOperation class and to the BulkExtensions class. In BulkUpdateOperation I've edited line 118 to look like this:
descriptors.Add(
new SetDescriptor(TypeInfo.Fields.First(a =>
member.DeclaringType.IsAssignableFrom(a.UnderlyingProperty.DeclaringType) &&
a.UnderlyingProperty.Name == member.Name), lambda.Parameters[0], lambda.Body));
and in BulkExtensions I've modified this method to do create a new BulkUpdateOperation for every hierarchy root, and passing the updateble's query through a .Select(x => x as type), and construct a new updateable of the correct type. It's really, really ugly.. But it's more proof och concept I think :)
public static int Update<T>(this IUpdatable<T> query) where T : class, IEntity
{
int result = 0;
var type = typeof(T);
var session = Session.Current;
var queryProvider = session.Query.Provider;
var allRoots = session.Domain.Model.Hierarchies.Select(x => x.Root);
var matchingRoots = allRoots.Where(x => type.IsAssignableFrom(x.UnderlyingType));
var typeBulkUpdateOperation = typeof(BulkUpdateOperation<>);
var updatable = (Updatable<T>)query;
var typeUpdatable = typeof(Updatable<>);
var queryableSelectMethodInfo = typeof(System.Linq.Queryable).GetMethods(
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).Where(x =>
x.Name == "Select" &&
x.GetParameters().Length == 2 &&
x.GetParameters()[1].ParameterType.GetGenericArguments().Length == 1 &&
x.GetParameters()[1].ParameterType.GetGenericArguments()[0].GetGenericArguments().Length == 2).Single();
var typeIQueryable = typeof(IQueryable<>);
foreach (var root in matchingRoots)
{
var c = Expression.Constant(updatable.Query);
var p = Expression.Parameter(type, "_x");
var typeAs = Expression.TypeAs(p, root.UnderlyingType);
var selectLambda = Expression.Lambda(typeAs, p);
var select = Expression.Call(queryableSelectMethodInfo.MakeGenericMethod(type, root.UnderlyingType), c, selectLambda);
var ps = Expression.Parameter(typeIQueryable.MakeGenericType(type), "_y");
var qryLambda = Expression.Lambda(select, ps);
var qry = qryLambda.Compile().DynamicInvoke(updatable.Query);
var up = Activator.CreateInstance(
typeUpdatable.MakeGenericType(root.UnderlyingType),
qry,
updatable.Expressions.First().Item1,
updatable.Expressions.First().Item2);
var typeBulkUpdateOperationTyped = typeBulkUpdateOperation.MakeGenericType(root.UnderlyingType);
var op = Activator.CreateInstance(typeBulkUpdateOperationTyped, up);
var executeMethod = typeBulkUpdateOperationTyped.GetMethod("Execute");
result += (int)executeMethod.Invoke(op, null);
}
return result;
//return new BulkUpdateOperation<T>(query).Execute();
}
I hope my research is useful :)
I've done a small test now, and as far as I can tell the field got updated in the db. And I do know I haven't added support for more then one .Set call, yet