Is it possible to use SQL Server table-value functions by using the BLToolkit library?
I would like to use it within the Linq query, but I couldn't find anything regarding this on the library wiki.
Define your function in your data context class as the following:
[TableFunction(Name="GetParentByID")]
public Table<Parent> GetParentByID(int? id)
{
return GetTable<Parent>(this, (MethodInfo)MethodBase.GetCurrentMethod(), id);
}
Usage:
[Test]
public void Func2()
{
using (var db = new TestDbManager())
{
var q =
from c in db.Child
from p in db.GetParentByID(2)
select p;
q.ToList();
}
}
SQL:
SELECT
[t2].[ParentID],
[t2].[Value1]
FROM
[Child] [t1], [GetParentByID](2) [t2]
Also you can define it outside of the data context:
public class Functions
{
private readonly IDataContext _ctx;
public Functions(IDataContext ctx)
{
_ctx = ctx;
}
[TableFunction]
public Table<Parent> GetParentByID(int? id)
{
return _ctx.GetTable<Parent>(this, (MethodInfo)(MethodBase.GetCurrentMethod()), id);
}
[TableExpression("{0} {1} WITH (TABLOCK)")]
public Table<T> WithTabLock<T>()
where T : class
{
return _ctx.GetTable<T>(this, ((MethodInfo)(MethodBase.GetCurrentMethod())).MakeGenericMethod(typeof(T)));
}
}
[Test]
public void Func1()
{
using (var db = new TestDbManager())
{
var q =
from p in new Functions(db).GetParentByID(1)
select p;
q.ToList();
}
}
[Test]
public void WithTabLock()
{
using (var db = new TestDbManager())
{
var q =
from p in new Functions(db).WithTabLock<Parent>()
select p;
q.ToList();
}
}
SQL:
SELECT
[p].[ParentID],
[p].[Value1]
FROM
[GetParentByID](1) [p]
SELECT
[p].[ParentID],
[p].[Value1]
FROM
[Parent] [p] WITH (TABLOCK)
Related
How may I avoid to duplicate the code I use for mapping a database entity to a poco object?
Given this code:
private IQueryable<DummyExtended> Find()
{
return (from dt in Entities.dummy_table
select new DummyExtended
{
Description = dt.table_1.table_2.description,
Dummy = new Dummy
{
Name = d.name,
Notes = d.notes,
HelpText = d.help_text
}
}).AsQueryable();
}
Can I create a common linq expression to be re-used for both methods?
private IQueryable<DummyExtended> Find()
{
return (from dt in Entities.dummy_table
select new DummyExtended
{
Description = dt.table_1.table_2.description,
Dummy = ...???
}).AsQueryable();
}
private IQueryable<DummyAlsoExtended> FindAnother()
{
return (from dt in Entities.dummy_table
select new DummyAlsoExtended
{
InnerHtml = dt.table_html.description,
Dummy = ....??
}).AsQueryable();
}
Example:
public static Expression<Func<dummy_table, Dummy>> EntityToPoco()
{
return d => new Dummy
{
Name = d.name,
Notes = d.notes,
HelpText = d.help_text
};
}
I can't quite get it right
....
Dummy = ExtensionClass.EntityToPoco()
So you have a dummy_table which is a Enumerable or Queryable sequence of objects. Let's assume that the sequence contains objects of class DummyTableElement.
You showed, that if you have a DummyTableElement you know how to convert it into a Dummy object. You want to reuse this function to create other objects like DummyExtended and DummyAlsoExtended. If you want to do this LINQ-alike, it is best to create extension functions for it:
static class DummyTableElementExtensions
{
public static Dummy ToDummy(this TableElement tableElement)
{
return new Dummy()
{
Name = tableElement.name,
Notes = tableElement.notes,
HelpText = tableElement.help_text
};
}
}
Once you have this, you can create similar functions to convert TableElements into DummyExtended and DummyAlsoExtended. They will be one-liners.
In the same extension class:
public static DummyExtended ToDummyExtended(this TableElement tableElement)
{
return new DummyExtended()
{
Description = tableElement.table_1.table_2.description,
Dummy = tableElement.ToDummy(),
};
}
public static DummyAlsoExtended ToDummyAlsoExtended(this TableElement tableElement)
{
return new DummyAlsoExtended
{
InnerHtml = tableElement.table_html.description,
Dummy = tableElement.ToDummy(),
};
}
And once you've got these, you can create extension functions to convert any IQueryable of TableElements:
public static IQueryable<DummyExtended> ToDummyExtended(
this IQueryable<TableElement> tableElements)
{
return tableElements
.Select(tableElement => tableelement.ToDummyExtended();
}
And a similar one-line function for DummyAlsoExtended.
Your Find function and FindAnother function will also be one-liners:
private IQueryable<DummyExtended> Find()
{
return dummy_table.ToDummyExtended();
}
private IQueryable<DummyAlsoExtended> FindAnother()
{
return dummy_table.ToDummyAlsoExtended();
}
I'm not sure why you wanted to use an expression in this. It doesn't seem that DummyExtended and DummyAlsoExtended are really similar, except that they both have a property Dummy.
One reason to parameterize the destination of your find function could be because you want to create anonymous classes in your Find function.
Again, once you've created ToDummy this will be a one-liner:
public static IQueryable<TResult> Find<TSource, TResult>(
this IQueryable<TSource> source,
Expression<Func<TSource, TResult>> resultSelector)
{
return source.Select(sourceElement => resultSelector(sourceElement);
}
Usage would be:
var X = dummy_Table.find(tableElement => new
{
foo = tableElement.CalculateFoo(),
bar = tableElement.CalculateBar(),
Dummy = tableElement.ToDummy(),
});
I downloaded Oracle Developer Tools for Visual studio and explored the libraries (.dll). I need to parse the PLSQL scripts to get the SQL Statements in that and using the visitors to visit the nodes. I found Oracle.VsDevTools.SqlAndPlsqlParser library and explored further and tried to parse the PLSQL scripts. Please refer the below code.
string plSQLScript="CREATE TABLE Student(Age int);";
List<LexerToken> tokens= LexerToken.Parse(plSQLScript, false, true);
But the above code gives tokens only, I need PLSQL parser.
Is there any possible way to parser the PLSQL scripts and get the sql statements. Also I need to explicit visit the sql statements like CreateTableVisitor, CreateProcedureVisitor, etc.
I already created the parser for TSQL files by exploring Microsoft SQL library called Microsoft.SqlServer.TransactSql.ScriptDom and using the below mentioned code for parsing.
TSql100Parser parser = new TSql100Parser(true);
IList<ParseError> error;
using (TextReader tr = new StreamReader(filePath))
{
TSqlFragment fragment = parser.Parse(tr, out error);
tr.Close();
}
And using TSqlFragmentVisitor to visit the sql statements. Please refer the below mentioned code.
public override void ExplicitVisit(CreateTableStatement node)
{
//----Coding----
}
I want the same for PLSQL by using Oracle.VsDevTools.SqlAndPlsqlParser library.
Please let me know if there any possibility for this.
Thanks,Sivaprakash.
With some reflection used it's possible. The parser, nodes and grammar classes are internal. I made small example for you:
private static Assembly odacDevAssembly = Assembly.Load("Oracle.VsDevTools.14.0");
private static Type parseNodeType = odacDevAssembly.GetType("Oracle.VsDevTools.SqlAndPlsqlParser.ParseNode");
void Main()
{
var parserType = odacDevAssembly.GetType("Oracle.VsDevTools.SqlAndPlsqlParser.OracleSqlEarley");
var parser = Activator.CreateInstance(parserType);
const string sqlScriptText = "DECLARE x NUMBER; BEGIN SELECT DUMMY INTO :DUMMY FROM DUAL T1; x := 1; SELECT DUMMY INTO :DUMMY FROM DUAL T2; x := 2; INSERT INTO T3 (C) VALUES (1); x := 3; END;";
var tokens = LexerToken.Parse(sqlScriptText);
var parserGrammar = parserType.GetProperty("EarleyGrammar").GetValue(parser);
var allSymbols = (string[])parserGrammar.GetType().GetField("m_vAllSymbols", BindingFlags.Public | BindingFlags.Instance).GetValue(parserGrammar);
var sqlStatementIndex = -1;
for (var i = 0; i < allSymbols.Length; i++)
{
if (allSymbols[i] == "unlabeled_nonblock_stmt")
{
sqlStatementIndex = i;
break;
}
}
var parseTree = parserType.GetMethod("Parse").Invoke(parser, new object[] { sqlScriptText, tokens });
//parseTree.GetType().GetMethod("PrintTree", new Type[] { parserGrammar.GetType() }).Invoke(parseTree, new object[] { parserGrammar });
var commandNodes = GetDescendants(parseTree).Where(d => GetPayloadIn(d) == sqlStatementIndex);
foreach (var commandNode in commandNodes)
{
var commandText = GetCommandText(commandNode, sqlScriptText, tokens);
Console.WriteLine(commandText);
}
}
private static string GetCommandText(object parseNode, string sqlScriptText, List<LexerToken> tokens)
{
var from = (int)parseNodeType.GetProperty("From").GetValue(parseNode);
var to = (int)parseNodeType.GetProperty("To").GetValue(parseNode) - 1;
var begin = tokens[from].m_vBegin;
return sqlScriptText.Substring(begin, tokens[to].m_vEnd - begin);
}
private static IEnumerable<object> GetDescendants(object parseNode)
{
yield return parseNode;
foreach (var child in GetChildren(parseNode))
foreach (var descendant in GetDescendants(child))
yield return descendant;
}
private static int GetPayloadIn(object parseNode)
{
return (int)parseNodeType.GetProperty("PayloadIn").GetValue(parseNode);
}
private static IEnumerable<object> GetChildren(object parseNode)
{
return (IEnumerable<object>)parseNodeType.GetMethod("Children").Invoke(parseNode, null);
}
UPDATE:
I'm not exactly sure what do you want to achieve but I hope it will be at least somehow helpful:
private static Assembly odacDevAssembly = Assembly.Load("Oracle.VsDevTools.14.0");
private static Type parseNodeType = odacDevAssembly.GetType("Oracle.VsDevTools.SqlAndPlsqlParser.ParseNode");
void Main()
{
var parserType = odacDevAssembly.GetType("Oracle.VsDevTools.SqlAndPlsqlParser.OracleSqlEarley");
var parser = Activator.CreateInstance(parserType);
const string sqlScriptText = "DECLARE x NUMBER; BEGIN SELECT DUMMY INTO :DUMMY FROM DUAL T1; x := 1; SELECT DUMMY INTO :DUMMY FROM DUAL T2; x := 2; INSERT INTO T3 (C) VALUES (1); x := 3; END;";
var tokens = LexerToken.Parse(sqlScriptText);
var parserGrammar = parserType.GetProperty("EarleyGrammar").GetValue(parser);
var allSymbols = (string[])parserGrammar.GetType().GetField("m_vAllSymbols", BindingFlags.Public | BindingFlags.Instance).GetValue(parserGrammar);
const int sqlStatementIndex = 4453; // unlabeled_nonblock_stmt
var parseTree = parserType.GetMethod("Parse").Invoke(parser, new object[] { sqlScriptText, tokens });
//parseTree.GetType().GetMethod("PrintTree", new Type[] { parserGrammar.GetType() }).Invoke(parseTree, new object[] { parserGrammar });
var commandNodes = GetDescendants(parseTree)
.Select(n => ParseNodeFactory.CreateNode(n, sqlScriptText, tokens))
.Where(n => n != null);
var visitor = new GrammarNodeVisitor();
foreach (var commandNode in commandNodes)
{
commandNode.Accept(visitor);
}
}
public static class ParseNodeFactory
{
private const int queryBlockIndex = 3849; // query_block
private const int insertIndex = 3136; // insert
public static IParseNode CreateNode(object parseNode, string sqlScriptText, List<LexerToken> tokens)
{
var symbolIndex = GetPayloadIn(parseNode);
var parseData = new ParseData { ParseNode = parseNode, SqlScriptText = sqlScriptText, Tokens = tokens };
switch (symbolIndex)
{
case queryBlockIndex:
return new QueryBlock { ParseData = parseData };
case insertIndex:
return new Insert { ParseData = parseData };
default:
return null;
}
}
}
public class GrammarNodeVisitor : IParseNodeVisitor
{
public void VisitQueryBlock(QueryBlock queryBlock)
{
Console.WriteLine($"Visited query block: {GetCommandText(queryBlock.ParseData.ParseNode, queryBlock.ParseData.SqlScriptText, queryBlock.ParseData.Tokens)}");
}
public void VisitInsert(Insert insert)
{
Console.WriteLine($"Visited insert command: {GetCommandText(insert.ParseData.ParseNode, insert.ParseData.SqlScriptText, insert.ParseData.Tokens)}");
}
}
public interface IParseNodeVisitor
{
void VisitQueryBlock(QueryBlock queryBlock);
void VisitInsert(Insert insert);
}
public interface IParseNode
{
ParseData ParseData { get; }
void Accept(IParseNodeVisitor visitor);
}
public class ParseData
{
public object ParseNode { get; set; }
public string SqlScriptText { get; set; }
public List<LexerToken> Tokens { get; set; }
}
public class QueryBlock : IParseNode
{
public ParseData ParseData { get; set; }
public void Accept(IParseNodeVisitor visitor)
{
visitor.VisitQueryBlock(this);
}
}
public class Insert : IParseNode
{
public ParseData ParseData { get; set; }
public void Accept(IParseNodeVisitor visitor)
{
visitor.VisitInsert(this);
}
}
private static string GetCommandText(object parseNode, string sqlScriptText, List<LexerToken> tokens)
{
var from = (int)parseNodeType.GetProperty("From").GetValue(parseNode);
var to = (int)parseNodeType.GetProperty("To").GetValue(parseNode) - 1;
var begin = tokens[from].m_vBegin;
return sqlScriptText.Substring(begin, tokens[to].m_vEnd - begin);
}
private static IEnumerable<object> GetDescendants(object parseNode)
{
yield return parseNode;
foreach (var child in GetChildren(parseNode))
foreach (var descendant in GetDescendants(child))
yield return descendant;
}
private static int GetPayloadIn(object parseNode)
{
return (int)parseNodeType.GetProperty("PayloadIn").GetValue(parseNode);
}
private static IEnumerable<object> GetChildren(object parseNode)
{
return (IEnumerable<object>)parseNodeType.GetMethod("Children").Invoke(parseNode, null);
}
I have a type which has a default sort order as it implements IComparable<T> and IComparable. I'm not getting the results I expect from LINQ , basically it looks as if the IComparable<T> which the type implements is not being applied.
I thought I would get the result I want with an expression in the form:
var result = MyEnumerable<T>.OrderBy(r => r);
where T itself implements IComparable<T>. It's not happening.
I can see related questions where specific IComparable<T> classes are specified for the sort, but I can't find one which uses the default IComparable<T> implemented by T itself.
My syntax is clearly incorrect. What is the correct syntax please?
Thanks in advance.
OrderBy uses the default comparer Comparer<T>.Default which in turn will default to use the IComparable<T> implementation for T, or the non-generic IComparable if the former does not exist.
This code works:
public class Program
{
static void Main(string[] args)
{
var list = new List<Stuff>
{
new Stuff("one"),
new Stuff("two"),
new Stuff("three"),
new Stuff("four")
};
var sorted = list.OrderBy(x => x);
foreach (var stuff in sorted)
{
Console.Out.WriteLine(stuff.Name);
}
}
}
public class Stuff : IComparable<Stuff>
{
public string Name { get; set; }
public Stuff(string name)
{
Name = name;
}
public int CompareTo(Stuff other)
{
return String.CompareOrdinal(Name, other.Name);
}
}
public static class GenericSorter
{
public static IOrderedEnumerable<T> Sort<T>(IEnumerable<T> toSort, Dictionary<string, SortingOrder> sortOptions)
{
IOrderedEnumerable<T> orderedList = null;
foreach (KeyValuePair<string, SortingOrder> entry in sortOptions)
{
if (orderedList != null)
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenBy");
}
else
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenByDescending");
}
}
else
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderBy");
}
else
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderByDescending");
}
}
}
return orderedList;
}
private static IOrderedEnumerable<T> ApplyOrder<T>(this IEnumerable<T> source, string property, string methodName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression expr = param;
foreach (string prop in property.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), expr.Type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
MethodInfo mi = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), expr.Type);
return (IOrderedEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
}
It's the first time that I use EmitMapper.
I have a list of object ex: Customer and I would like to map this list in a ienumerable of CustomerDTO how can I do that?
Tnx
It's straightforward if you have a list and want to convert it to list of DTOs:
var mapper = ObjectMapperManager.DefaultInstance.GetMapper<Customer, CustomerDTO>();
IEnumerable<CustomerDTO> dtos = listOfCustomer.Select(mapper.map);
The preblem is when the list is in another object, for example User and UserDTO:
class User {
public List<Customer> Customers { get; set; }
}
class UserDTO {
public IEnumerable<CustomerDTO> Customers { get; set; }
}
It seems that EmitMapper does not support conversion from List to Enumerable. A way to support it would be:
var customerMapper = ObjectMapperManager
.DefaultInstance.GetMapper<Customer, CustomerDTO>();
var mapper = ObjectMapperManager.DefaultInstance
.GetMapper<User, UserDTO>(
new DefaultMapConfig()
.ConvertUsing<List<Customer>, IEnumerable<CustomerDTO>>(
a => a.Select(customerMapper.Map))
);
This can be done creating a custom class, implementing the interface "ICustomConverterProvider" and adding a ConvertGeneric to the "DefaultMapConfig".
Looking on the source code of EmitMapper, i found a class named "ArraysConverterProvider", which is the default generic converter from ICollections to Arrays.
Adapting the code from this class to work with IEnumerable collections:
class GenericIEnumerableConverterProvider : ICustomConverterProvider
{
public CustomConverterDescriptor GetCustomConverterDescr(
Type from,
Type to,
MapConfigBaseImpl mappingConfig)
{
var tFromTypeArgs = DefaultCustomConverterProvider.GetGenericArguments(from);
var tToTypeArgs = DefaultCustomConverterProvider.GetGenericArguments(to);
if (tFromTypeArgs == null || tToTypeArgs == null || tFromTypeArgs.Length != 1 || tToTypeArgs.Length != 1)
{
return null;
}
var tFrom = tFromTypeArgs[0];
var tTo = tToTypeArgs[0];
if (tFrom == tTo && (tFrom.IsValueType || mappingConfig.GetRootMappingOperation(tFrom, tTo).ShallowCopy))
{
return new CustomConverterDescriptor
{
ConversionMethodName = "Convert",
ConverterImplementation = typeof(GenericIEnumerableConverter_OneTypes<>),
ConverterClassTypeArguments = new[] { tFrom }
};
}
return new CustomConverterDescriptor
{
ConversionMethodName = "Convert",
ConverterImplementation = typeof(GenericIEnumerableConverter_DifferentTypes<,>),
ConverterClassTypeArguments = new[] { tFrom, tTo }
};
}
}
class GenericIEnumerableConverter_DifferentTypes<TFrom, TTo> : ICustomConverter
{
private Func<TFrom, TTo> _converter;
public IEnumerable<TTo> Convert(IEnumerable<TFrom> from, object state)
{
if (from == null)
{
return null;
}
TTo[] result = new TTo[from.Count()];
int idx = 0;
foreach (var f in from)
{
result[idx++] = _converter(f);
}
return result;
}
public void Initialize(Type from, Type to, MapConfigBaseImpl mappingConfig)
{
var staticConverters = mappingConfig.GetStaticConvertersManager() ?? StaticConvertersManager.DefaultInstance;
var staticConverterMethod = staticConverters.GetStaticConverter(typeof(TFrom), typeof(TTo));
if (staticConverterMethod != null)
{
_converter = (Func<TFrom, TTo>)Delegate.CreateDelegate(
typeof(Func<TFrom, TTo>),
null,
staticConverterMethod
);
}
else
{
_subMapper = ObjectMapperManager.DefaultInstance.GetMapperImpl(typeof(TFrom), typeof(TTo), mappingConfig);
_converter = ConverterBySubmapper;
}
}
ObjectsMapperBaseImpl _subMapper;
private TTo ConverterBySubmapper(TFrom from)
{
return (TTo)_subMapper.Map(from);
}
}
class GenericIEnumerableConverter_OneTypes<T>
{
public IEnumerable<T> Convert(IEnumerable<T> from, object state)
{
if (from == null)
{
return null;
}
return from;
}
}
This code is just a copy with a minimum of adaptation as possible and can be applyed to objects with many levels of hierarchy.
You can use the above code with the following command:
new DefaultMapConfig().ConvertGeneric(
typeof(IEnumerable<>),
typeof(IEnumerable<>),
new GenericIEnumerableConverterProvider());
This saved my day and I hope to save yours too! hehehe
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 }
}
});
}
}