Observable that combines behavior of groupby with combinelatest and exhibits "outer join" behavior - rxjs

I'm trying to use reactive paradigm to create an observable that acts like a combination of "group by" and combinelatest. I have two source observables that have a shared joining key, like in the following two data structures.
class Foo
{
string Key;
string Funky;
}
class Bar
{
string Key;
string Town;
}
What I want is an observable that yields me the latest combination of these two joined on InstrumentID. The end result should look something like:
class Target
{
string Key;
string Funky;
string Town;
}
and exhibits an "outer join" like behavior, meaning the first sequence to produce a new "key" will yield a Target class with the other side being null, and then once the other side also produces the same joining key, the latest from both sides is yielded whenever there's a new value in either sequence for the given key.

Let's say your foo$ stream emits values of type Foo, and bar$ stream emits values of Bar.
Here is how you can combine them:
combineLatest([
foo$,
bar$
// use startWith(null) to ensure combineLatest will emit as soon as foo$ emits, not waiting for bar$ to emit its first value
.pipe(startWith(null))
]).pipe(
map(([foo, bar]) => ({
// always keep all properties from foo
...foo,
// only add properties from bar if it has the matching Key
...(bar && bar.Key === foo.Key ? bar : null)
}))
)

This may not be "cosher" by some standards but works for what I need it to do. Posting for anyone looking for same functionality (.NET Version of RX)
public static class Extensions
{
public static IObservable<TResult> CombineLatestGrouped<TFirst,TSecond,TKey, TResult>(
this IObservable<TFirst> first,
IObservable<TSecond> second,
Func<TFirst, TKey> firstKeySelector,
Func<TSecond, TKey> secondKeySelector,
Func<TKey,TFirst,TSecond,TResult> resultSelector)
{
var dic = new ConcurrentDictionary<TKey,Tuple<TFirst,TSecond>>();
return Observable.Create<TResult>(obs =>
{
var d1 = first
.Select(x =>
{
var key = firstKeySelector(x);
var tuple = dic.AddOrUpdate(
key,
addValueFactory: key => Tuple.Create(x, default(TSecond)),
updateValueFactory: (key, existing) => Tuple.Create(x, existing.Item2));
return resultSelector(key, tuple.Item1, tuple.Item2);
})
.Subscribe(obs);
var d2 = second
.Select(x =>
{
var key = secondKeySelector(x);
var tuple = dic.AddOrUpdate(
key,
addValueFactory: key => Tuple.Create(default(TFirst), x),
updateValueFactory: (key, existing) => Tuple.Create(existing.Item1, x));
return resultSelector(key, tuple.Item1, tuple.Item2);
})
.Subscribe(obs);
return new CompositeDisposable(d1, d2);
});
}
}

As I see it, you would like following:
a new "key" will yield a Target class with the other side being null
When left or right side emmits a NEW key (prev: null or different)
, and then once the other side also produces the same joining key,
precondition: a stream emitted a value -- other stream now emits a value and the key for left and right eq
the latest from both sides is yielded whenever there's a new value in either sequence for the given key.
emit full target (composed of left, right) on each left,right emit, when a value of left,right changes distinctly
RxJava2 solution for my assumption:
#Test
void test2() {
PublishSubject<Foo> foo$ = PublishSubject.create();
PublishSubject<Bar> bar$ = PublishSubject.create();
Observable<Target> target$ = Observable.merge(Arrays.asList(foo$, bar$))
// filter invalid values
.filter(hasId -> hasId.key() != null)
.scan(Target.NULL, (prev, change) -> {
// when prev. target and current value#key are eq -> emit composed value
if (change.key().equals(prev.key)) {
return composedTarget(prev, change);
} else if (change instanceof Foo) {
return Target.fromFoo((Foo) change);
} else if (change instanceof Bar) {
return Target.fromBar((Bar) change);
}
return prev;
}).filter(target -> target != Target.NULL)
.distinctUntilChanged();
TestObserver<Target> test = target$.test();
// emit
foo$.onNext(new Foo("123", "f1"));
// emit
bar$.onNext(new Bar("123", "f2"));
// emit
bar$.onNext(new Bar("123", "f3"));
// skipped
foo$.onNext(new Foo("123", "f1"));
// emit
foo$.onNext(new Foo("123", "f5"));
// emit
foo$.onNext(new Foo("key", "value"));
// emit
foo$.onNext(new Foo("key2", "value2"));
// emit
bar$.onNext(new Bar("bar2", "Berlin"));
// emit
foo$.onNext(new Foo("foo2", "Funkeey"));
test.assertValues(
new Target("123", "f1", null),
new Target("123", "f1", "f2"),
new Target("123", "f1", "f3"),
new Target("123", "f5", "f3"),
new Target("key", "value", null),
new Target("key2", "value2", null),
new Target("bar2", null, "Berlin"),
new Target("foo2", "Funkeey", null)
);
}
private Target composedTarget(Target prev, HasId change) {
if (change instanceof Foo) {
Foo foo = (Foo) change;
return new Target(prev.key, foo.funky, prev.town);
}
if (change instanceof Bar) {
Bar bar = (Bar) change;
return new Target(prev.key, prev.funky, bar.town);
}
return prev;
}
Domain-Classes
interface HasId {
String key();
}
static final class Foo implements HasId {
final String key;
final String funky;
Foo(String key, String funky) {
this.key = key;
this.funky = funky;
}
#Override
public String key() {
return key;
}
}
static final class Bar implements HasId {
String key;
String town;
Bar(String key, String town) {
this.key = key;
this.town = town;
}
#Override
public String key() {
return key;
}
}
static final class Target {
private static final Target NULL = new Target(null, null, null);
final String key;
final String funky;
final String town;
Target(String key, String funky, String town) {
this.key = key;
this.funky = funky;
this.town = town;
}
static Target fromFoo(Foo foo) {
return new Target(foo.key, foo.funky, null);
}
static Target fromBar(Bar bar) {
return new Target(bar.key, null, bar.town);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Target target = (Target) o;
return key.equals(target.key) &&
Objects.equals(funky, target.funky) &&
Objects.equals(town, target.town);
}
#Override
public int hashCode() {
return Objects.hash(key, funky, town);
}
#Override
public String toString() {
return "Target{" +
"key='" + key + '\'' +
", funky='" + funky + '\'' +
", town='" + town + '\'' +
'}';
}
}
Please correct my assumptions, if I am mistaken. The solution could be implemented way better in C# with pattern-matching. Actually if C# has union types like F#, that would be best.

Related

How to group objects in Java 8

WalletCreditNoteVO a1 = new WalletCreditNoteVO(1L, 1L, "A", WalletCreditNoteStatus.EXPIRED, null, null, CreditNoteType.CAMPAIGN_VOUCHER, BigDecimal.ONE, BigDecimal.ONE, "GBP");
WalletCreditNoteVO a2 = new WalletCreditNoteVO(1L, 1L, "A", WalletCreditNoteStatus.EXPIRED, null, null, CreditNoteType.CAMPAIGN_VOUCHER, BigDecimal.ONE, BigDecimal.TEN, "GBP");
WalletCreditNoteVO a3 = new WalletCreditNoteVO(2L, 1L, "A", WalletCreditNoteStatus.EXPIRED, null, null, CreditNoteType.CAMPAIGN_VOUCHER, BigDecimal.ONE, BigDecimal.ONE, "GBP");
WalletCreditNoteVO a4 = new WalletCreditNoteVO(2L, 1L, "A", WalletCreditNoteStatus.EXPIRED, null, null, CreditNoteType.CAMPAIGN_VOUCHER, BigDecimal.ONE, BigDecimal.TEN, "GBP");
final List<WalletCreditNoteVO> walletCreditNoteVOs = Lists.newArrayList(a1, a2, a3, a4);
Map<WalletCreditNoteVO, BigDecimal> collect2 = walletCreditNoteVOs.stream().collect(
groupingBy(wr -> new WalletCreditNoteVO(wr.getCreditNoteId(), wr.getWalletCustomerId(), wr.getCreditNoteTitle(),
wr.getWalletCreditNoteStatus(), wr.getCreditNoteStartDate(), wr.getCreditNoteExpiryDate(), wr.getCreditNoteType(), wr.getCreditNoteValue(), wr.getCurrency()),
mapping(WalletCreditNoteVO::getAvailableBalance,
reducing(BigDecimal.ZERO, (sum, elem) -> sum.add(elem)))));
I want to introduce condition for final reducing to be either sum (as written above) or last value in the list of BigDecimal based on the status of getWalletCreditNoteStatus
Can someone please help.
Thanks #xiumeteo . Below is improved solution
Function<WalletCreditNoteVO, WalletCreditNoteVO> function = wr -> new WalletCreditNoteVO(wr.getCreditNoteId(), wr.getWalletCustomerId(), wr.getCreditNoteTitle(),
wr.getWalletCreditNoteStatus(), wr.getCreditNoteStartDate(), wr.getCreditNoteExpiryDate(), wr.getCreditNoteType(), wr.getCreditNoteValue(), wr.getCurrency());
final Map<WalletCreditNoteVO, BigDecimal> collectMap =
walletCreditNoteVOs.stream()
.collect(groupingBy(function, LinkedHashMap::new, Collectors.collectingAndThen(
toList(),
(list) -> {
final List<BigDecimal> availableBalances = list.stream().map(WalletCreditNoteVO::getAvailableBalance).collect(toList());
if (list.stream().allMatch(WalletCreditNoteVO::isStatusExpired)) {
return availableBalances.stream().filter(o -> o != null).reduce((a, b) -> b).orElse(null).abs();
} else {
return availableBalances.stream().filter(o -> o != null).reduce(BigDecimal.ZERO, BigDecimal::add);
}
})));
List<WalletCreditNoteVO> walletCreditNoteVOGrouped = new ArrayList<>();
for(Map.Entry<WalletCreditNoteVO, BigDecimal> entry : collectMap.entrySet()){
WalletCreditNoteVO key = entry.getKey();
key.setAvailableBalance(entry.getValue());
walletCreditNoteVOGrouped.add(key);
}
I now want to remove 'for loop' and stream logic should just give me one list of WalletCreditNoteVO instead of Map of WalletCreditNoteVO as key and BigDecimal as value, with value set directly in the WalletCreditNoteVO
Thanks all again (I can't add code in my comments so adding it here).
So I did a test for your case, I created a dummy class that resembles yours:
public static class Something{
private String name;
private Integer sum;
private boolean checker;
public Something(String name, Integer sum, boolean checker) {
this.name = name;
this.sum = sum;
this.checker = checker;
}
public String getName() {
return name;
}
public boolean isChecker() {
return checker;
}
public Integer getSum() {
return sum;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Something something = (Something) o;
return new EqualsBuilder().append(getName(), something.getName()).append(getSum(), something.getSum()).isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(getName()).append(getSum()).toHashCode();
}
}
And then I did this little test
List<Something> items = Arrays.asList(new Something("name", 10, false), new Something("name", 14, true), new Something("name", 11, false),
new Something("name", 11, false), new Something("noName", 12, false));
final Map<Something, Integer> somethingToSumOrLastElement =
items.stream()
.collect(Collectors.groupingBy(Function.identity(),
Collectors.collectingAndThen(
Collectors.toList(), // first we collect all your related items into a list
(list) -> { //this collector allow us to have a finisher, Function<List<Something>, Object>, let's define it
final List<Integer> integerStream = list.stream().map(Something::getSum).collect(Collectors.toList());
if (list.stream().allMatch(Something::isChecker)) { // we check for the method you want to check
//you have to change this depending on required logic
//for this case if that's true for every element in the list, we do the reduce by summing
return integerStream.stream().reduce(0, (sum, next) -> sum + next);
}
//if not, we just get the last element of that list
return integerStream.stream().reduce(0, (sum, next) -> next);
})));
I think this is ok, but maybe someone has a better idea on how to handle your issue.
Ping me if you need clarification :)

A List of Field Names as a String Array - LINQ Expressions

Hello MVC and LINQ Experts,
I have a Model that looks like this:
public class SomeClass : IValidatableObject
{
public string SomeString { get; set; }
public string SomeString2 { get; set; }
public int SomeInteger { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//... IF there is some error...THEN
yield return new ValidationResult("Some Error Message.", GetFieldNames(() => new []{ this.SomeString }));
}
}
As you can see, I am calling GetFieldNames that takes an expression, and returns to you the expression members as a string array. According to a book I read recently, the way to link an error to a field is to pass it as a string as follows:
yield return new ValidationResult("Some Error Message.", new []{ "SomeString" }));
But I wanted to be Strongly Typed, so here is the method that I wrote:
public static string[] GetFieldNames(Expression<Func<object[]>> exp)
{
//Build a string that will in the end look like this: field1,field2,field3
//Then we split(',') it into an array and return that string array.
string fieldnames = "";
MemberExpression body = exp.Body as MemberExpression;
if (body == null)
{
NewArrayExpression ubody = (NewArrayExpression)exp.Body;
foreach(MemberExpression exp2 in ubody.Expressions)
{
fieldnames += exp2.Member.Name + ",";
}
fieldnames = fieldnames.TrimEnd(',');
}
if(fieldnames.Length > 0)
return fieldnames.Split(',');
else
return new string[]{};
}
Current Usage:
GetFieldNames(() => new[] { this.SomeString , this.SomeString2 });
Output:
{ "SomeString" , "SomeString2" }
This works fine.
The problem is that if I use it as follows, it gives me an error (compile time):
GetFieldNames(() => new[] { this.SomeString , this.SomeInteger });
Error:
No best type found for implicitly-typed array
My Desired Output:
{ "SomeString" , "SomeInteger" }
I can't pass in an array of object because int is not a complex type.
How can I pass the function an expression array with int and string?
You could try passing an array of objects (which is what your expression expects) instead of trying to use an array initializer syntax:
GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger });
This allows you to pass arbitrary object types.
You could define an interface IFieldName that enables usage in your list, and then implement it in different classes (int, error, string, etc.) for the actual types that occur in your processing.
This is roughly equivalent to defining an rray of object, but restores type-safety.
With the help of Darin Dimitri (the idea to pass a new object[] instead of new []
The following code will make sure that your IValidatableObject can now be strongly typed instead of just an array of strings.
public static string[] GetFieldNames(Expression<Func<object[]>> exp)
{
string fieldnames = "";
MemberExpression body = exp.Body as MemberExpression;
if (body == null)
{
NewArrayExpression ubody = (NewArrayExpression)exp.Body;
foreach (Expression exp2 in ubody.Expressions)
{
if (exp2 is MemberExpression) {
fieldnames += ((MemberExpression)exp2).Member.Name + ",";
}
else {
var op = ((UnaryExpression)exp2).Operand;
fieldnames += ((MemberExpression)op).Member.Name + ",";
}
}
fieldnames = fieldnames.TrimEnd(',');
}
if(fieldnames.Length > 0)
return fieldnames.Split(',');
else
return new string[]{};
}
Usage:
GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger }));
Usage for MVC Validation:
yield return new ValidationResult("Some Error.", GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger }));

Trying to save comma-separated list

Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();

Partition/split/section IEnumerable<T> into IEnumerable<IEnumerable<T>> based on a function using LINQ?

I'd like to split a sequence in C# to a sequence of sequences using LINQ. I've done some investigation, and the closest SO article I've found that is slightly related is this.
However, this question only asks how to partition the original sequence based upon a constant value. I would like to partition my sequence based on an operation.
Specifically, I have a list of objects which contain a decimal property.
public class ExampleClass
{
public decimal TheValue { get; set; }
}
Let's say I have a sequence of ExampleClass, and the corresponding sequence of values of TheValue is:
{0,1,2,3,1,1,4,6,7,0,1,0,2,3,5,7,6,5,4,3,2,1}
I'd like to partition the original sequence into an IEnumerable<IEnumerable<ExampleClass>> with values of TheValue resembling:
{{0,1,2,3}, {1,1,4,6,7}, {0,1}, {0,2,3,5,7}, {6,5,4,3,2,1}}
I'm just lost on how this would be implemented. SO, can you help?
I have a seriously ugly solution right now, but have a "feeling" that LINQ will increase the elegance of my code.
Okay, I think we can do this...
public static IEnumerable<IEnumerable<TElement>>
PartitionMontonically<TElement, TKey>
(this IEnumerable<TElement> source,
Func<TElement, TKey> selector)
{
// TODO: Argument validation and custom comparisons
Comparer<TKey> keyComparer = Comparer<TKey>.Default;
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
yield break;
}
TKey currentKey = selector(iterator.Current);
List<TElement> currentList = new List<TElement> { iterator.Current };
int sign = 0;
while (iterator.MoveNext())
{
TElement element = iterator.Current;
TKey key = selector(element);
int nextSign = Math.Sign(keyComparer.Compare(currentKey, key));
// Haven't decided a direction yet
if (sign == 0)
{
sign = nextSign;
currentList.Add(element);
}
// Same direction or no change
else if (sign == nextSign || nextSign == 0)
{
currentList.Add(element);
}
else // Change in direction: yield current list and start a new one
{
yield return currentList;
currentList = new List<TElement> { element };
sign = 0;
}
currentKey = key;
}
yield return currentList;
}
}
Completely untested, but I think it might work...
alternatively with linq operators and some abuse of .net closures by reference.
public static IEnumerable<IEnumerable<T>> Monotonic<T>(this IEnumerable<T> enumerable)
{
var comparator = Comparer<T>.Default;
int i = 0;
T last = default(T);
return enumerable.GroupBy((value) => { i = comparator.Compare(value, last) > 0 ? i : i+1; last = value; return i; }).Select((group) => group.Select((_) => _));
}
Taken from some random utility code for partitioning IEnumerable's into a makeshift table for logging. If I recall properly, the odd ending Select is to prevent ambiguity when the input is an enumeration of strings.
Here's a custom LINQ operator which splits a sequence according to just about any criteria. Its parameters are:
xs: the input element sequence.
func: a function which accepts the "current" input element and a state object, and returns as a tuple:
a bool stating whether the input sequence should be split before the "current" element; and
a state object which will be passed to the next invocation of func.
initialState: the state object that gets passed to func on its first invocation.
Here it is, along with a helper class (required because yield return apparently cannot be nested):
public static IEnumerable<IEnumerable<T>> Split<T, TState>(
this IEnumerable<T> xs,
Func<T, TState, Tuple<bool, TState>> func,
TState initialState)
{
using (var splitter = new Splitter<T, TState>(xs, func, initialState))
{
while (splitter.HasNext)
{
yield return splitter.GetNext();
}
}
}
internal sealed class Splitter<T, TState> : IDisposable
{
public Splitter(IEnumerable<T> xs,
Func<T, TState, Tuple<bool, TState>> func,
TState initialState)
{
this.xs = xs.GetEnumerator();
this.func = func;
this.state = initialState;
this.hasNext = this.xs.MoveNext();
}
private readonly IEnumerator<T> xs;
private readonly Func<T, TState, Tuple<bool, TState>> func;
private bool hasNext;
private TState state;
public bool HasNext { get { return hasNext; } }
public IEnumerable<T> GetNext()
{
while (hasNext)
{
Tuple<bool, TState> decision = func(xs.Current, state);
state = decision.Item2;
if (decision.Item1) yield break;
yield return xs.Current;
hasNext = xs.MoveNext();
}
}
public void Dispose() { xs.Dispose(); }
}
Note: Here are some of the design decisions that went into the Split method:
It should make only a single pass over the sequence.
State is made explicit so that it's possible to keep side effects out of func.

How to retrieve ordering information from IQueryable object?

Let's say, I have an instance of IQueryable. How can I found out by which parameters it was ordered?
Here is how OrderBy() method looks like (as a reference):
public static IOrderedQueryable<T> OrderBy<T, TKey>(
this IQueryable<T> source, Expression<Func<T, TKey>> keySelector)
{
return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(
Expression.Call(null,
((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(
new Type[] { typeof(T), typeof(TKey) }
),
new Expression[] { source.Expression, Expression.Quote(keySelector) }
)
);
}
A hint from Matt Warren:
All queryables (even IOrderedQueryable's) have expression trees underlying them that encode the activity they represent. You should find using the IQueryable.Expression property a method-call expression node representing a call to the Queryable.OrderBy method with the actual arguments listed. You can decode from the keySelector argument the expression used for ordering. Take a look at the IOrderedQueryable object instance in the debugger to see what I mean.
This isn't pretty, but it seems to do the job:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Windows.Forms;
public class Test
{
public int A;
public string B { get; set; }
public DateTime C { get; set; }
public float D;
}
public class QueryOrderItem
{
public QueryOrderItem(Expression expression, bool ascending)
{
this.Expression = expression;
this.Ascending = ascending;
}
public Expression Expression { get; private set; }
public bool Ascending { get; private set; }
public override string ToString()
{
return (Ascending ? "asc: " : "desc: ") + Expression;
}
}
static class Program
{
public static List<QueryOrderItem> GetQueryOrder(Expression expression)
{
var members = new List<QueryOrderItem>(); // queue for easy FILO
GetQueryOrder(expression, members, 0);
return members;
}
static void GetQueryOrder(Expression expr, IList<QueryOrderItem> members, int insertPoint)
{
if (expr == null) return;
switch (expr.NodeType)
{
case ExpressionType.Call:
var mce = (MethodCallExpression)expr;
if (mce.Arguments.Count > 1)
{ // OrderBy etc is expressed in arg1
switch (mce.Method.Name)
{ // note OrderBy[Descending] shifts the insertPoint, but ThenBy[Descending] doesn't
case "OrderBy": // could possibly check MemberInfo
members.Insert(insertPoint, new QueryOrderItem(mce.Arguments[1], true));
insertPoint = members.Count; // swaps order to enforce stable sort
break;
case "OrderByDescending":
members.Insert(insertPoint, new QueryOrderItem(mce.Arguments[1], false));
insertPoint = members.Count;
break;
case "ThenBy":
members.Insert(insertPoint, new QueryOrderItem(mce.Arguments[1], true));
break;
case "ThenByDescending":
members.Insert(insertPoint, new QueryOrderItem(mce.Arguments[1], false));
break;
}
}
if (mce.Arguments.Count > 0)
{ // chained on arg0
GetQueryOrder(mce.Arguments[0], members, insertPoint);
}
break;
}
}
static void Main()
{
var data = new[] {
new Test { A = 1, B = "abc", C = DateTime.Now, D = 12.3F},
new Test { A = 2, B = "abc", C = DateTime.Today, D = 12.3F},
new Test { A = 1, B = "def", C = DateTime.Today, D = 10.1F}
}.AsQueryable();
var ordered = (from item in data
orderby item.D descending
orderby item.C
orderby item.A descending, item.B
select item).Take(20);
// note: under the "stable sort" rules, this should actually be sorted
// as {-A, B, C, -D}, since the last order by {-A,B} preserves (in the case of
// a match) the preceding sort {C}, which in turn preserves (for matches) {D}
var members = GetQueryOrder(ordered.Expression);
foreach (var item in members)
{
Console.WriteLine(item.ToString());
}
// used to investigate the tree
TypeDescriptor.AddAttributes(typeof(Expression), new[] {
new TypeConverterAttribute(typeof(ExpandableObjectConverter)) });
Application.Run(new Form
{
Controls = {
new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = ordered.Expression }
}
});
}
}

Resources