Using dynamic objects in codedom created LINQ queries - c#

I tried to use expandoobjects in LINQ queries to have the ability to query against properties that are created during runtime, for example the headers from a csv file. It all worked fine if typing the LINQ query direct in the code as in the example:
// initialize testdata
List<ExpandoObject> hans = new List<ExpandoObject>();
string[] names = {"Apfel", "Birne", "Banane", "Orange"};
int[] ids = { 1, 2, 3, 4 };
for (int i = 0; i < 4; i++)
{
dynamic horst = new ExpandoObject();
((IDictionary<string, object>)horst).Add("Fruit", names[i]);
((IDictionary<string, object>)horst).Add("ID", ids[i]);
hans.Add(horst);
}
// try some LINQ queries, both are working as intended
var klaus = from dynamic x in hans where x.ID < 3 select x;
//var klaus = hans.Where(x => x.ID < 3).Select(x => x);
Then i tried to read the query from the commandline and create a dynamic LINQ query using a slighty modified version of the evaluant linq compiler.
string expression = System.Console.ReadLine();
LinqCompiler lc = new LinqCompiler(expression);
lc.AddSource<ExpandoObject>("hans", hans);
IEnumerable<ExpandoObject> klaus = (IEnumerable<ExpandoObject>)lc.Evaluate();
As long as as i donĀ“t use WHERE or ORDER BY statements, everything is fine, but if any WHERE or ORDER BY is included in the query, i get an error when compiling the codedom code in the linq compiler: CS1963: An expression tree may not contain a dynamic operation.
The code for the query is created using the following line:
doRequestMethod.Statements.Add(new CodeMethodReturnStatement(new CodeSnippetExpression(Query)));
I suppose that the codedom compiler is building the expression tree in some way different to the way a direct typed in LINQ query is parsed. Any idea to get this to work would be appretiated, including other ideas to dynamically create queries for runtime-generated objects.

To get the error you're getting, I had to fix the LINQ Compiler to support dynamic, by telling it to use C# 4.0 and add a reference to Microsoft.CSharp.dll, so I assume you have done the same.
The problem is a source in LINQ compiler can be any collection, including IQueryable<T>. And if IQueryable<T> is supposed to work correctly, you actually need to treat it as IQueryable<T>, not IEnumerable<T>. The way LINQ compiler solves this is that it treats any source as IQuerybale<T>by using the AsQueryable() extension method.
This means the generated code looks like this:
public object DoRequest(System.Linq.IQueryable<System.Dynamic.ExpandoObject> hans) {
return from dynamic x in hans where x.ID < 3 select x;
}
The problem with this code is that tries to use IQuerybale<T> versions of LINQ methods, that use Expressions. And, as the error message tells you, Expressions don't support dynamic.
I think the easiest way to fix this is to modify LINQ Compiler to use IEnumerable<T>, instead of IQuerybale<T> by changing the AddSource() into:
public void AddSource<T>(string name, IEnumerable<T> source)
{
this.sources.Add(new SourceDescription(name, typeof(IEnumerable<T>), source));
}
Of course, this means it won't work well for database queries, but you can't make database queries work with dynamic anyway.

Related

Dynamic entity framework linq

I'm trying to create a method that will generate EF Core linq queries dynamically which I intend to use on a generic page in my app.
So far I have this...
public static IQueryable<object> LoadGridData(this DbContext context, GridLoadParameters parameters)
{
List<string> includes = new List<string>();
foreach (string str in parameters.Columns)
{
string path = str.StripLastElement('.');
if (!string.IsNullOrEmpty(path) && !includes.Contains(path))
includes.Add(path);
}
includes = includes.OrderBy(x => x).ToList();
IQueryable<object> linq = ((IQueryable)context.PropertyByName(parameters.Table.Pluralize())).OfType<object>();
foreach (string path in includes)
linq = linq.Include(path);
foreach (OrderColumn order in parameters.Order)
if (order.Direction == OrderDirection.Ascending)
linq = ((IQueryable)linq).Cast<object>().OrderBy(x => x.PropertyByName(order.ColumnPath));
else
linq = ((IQueryable)linq).Cast<object>().OrderByDescending(x => x.PropertyByName(order.ColumnPath));
linq = linq.Skip(parameters.ItemsToSkip).Take(parameters.ItemsToTake)
.Select(CreateLambdaObjectArraySelect<object>(parameters.Columns));
return linq;
}
The problem I have is with the Include(path) line. To keep the query dynamic I have had to cast the result to type 'object'. This is fine when accessing the properties because I have an extension method 'PropertyByName' which uses reflection to retrieve the value.
However, the Include(path) line needs to understand the entity type in order to work and as all the results have been cast to object it fails with the error 'The Include operation 'Include("Period")' is not supported. 'Period' must be a navigation property defined on an entity type.'
I have considered using a dynamic linq library but I really wanted to see if I could solve the problem without it first. After all, if the library can do it, there must be a way!

C# equivalent of Lambda

I'm trying to convert some JS into C# and i got to this piece but cant figure out what a C# equivalent would be. Hoping someone can point me in the right direction?
I just need help with the contents of these two functions. The $iterator is coded in another spot but im guessing that the C# version of the following code doesnt need it. If you need me to add it, i can.
The context these functions are being called is:
var centers = Lambda.array(Lambda.map(this.hexes,function(hex) {
return me.hexToCenter(hex);
}));
And the functions are:
var Lambda = function() { }
Lambda.array = function(it) {
var a = new Array();
var $it0 = $iterator(it)();
while( $it0.hasNext() ) {
var i = $it0.next();
a.push(i);
}
return a;
}
Lambda.map = function(it,f) {
var l = new List();
var $it0 = $iterator(it)();
while( $it0.hasNext() ) {
var x = $it0.next();
l.add(f(x));
}
return l;
}
You don't really need your own map and array methods. There is already the same functionality available, you just have to add using System.Linq; at the top of your file and you'll be able to use both Select, which is a projection method and ToArray which created an array from your collection. They are both extension methods set on IEnumerable<T>, so you can use them on almost any collection.
var centers = hexes.Select(x => me.hexToCenter(x)).ToArray();
is an equivalent of you JavaScript code:
var centers = Lambda.array(Lambda.map(this.hexes,function(hex) {
return me.hexToCenter(hex);
}));
This looks like a fairly simple C# lambda:
var centers = this.hexes.Select(hex => me.hexToCenter(hex)).ToList();
Select and ToList extension methods are provided by LINQ - you need to add using System.Linq to use them.
You probably want to go the LINQ route here.
Your map is equivalent to LINQ's Select, e.g.
var centers = this.hexes.Select(hex => me.hexToCenter(hex)).ToArray();
The expression hex => me.hexToCenter(hex) is a lambda expression in C#, which Select uses to project this.hexes into your desired form.
ToArray() is equivalent to your Lambda.array call.
101 LINQ Samples in C# is a great resource for examples on using LINQ.
note most of the 101 samples use the query syntax as opposed to the functional syntax I've used above. They are roughly equivalent for simple cases, but being comfortable with the functional syntax shouldn't be a problem for you, coming from JS background.

SQLiteNet Index and Lambda expressions

We are using Xamarin with SQLiteNet as ORM.
In our data layer class we have the method below.
filter = ri => ri.ItemVersioniId == itemVersionId;
The method is getting the records matching the Id. If the lambda expression is hardcoded, instead of using the "filter" parameter it is much faster... even though it is the same logic.
We would to be able to pass the filter as a parameter but still get a good performance. Any advise?
public virtual List<ResourceItem> GetResourceItems (string itemVersionId, Func<ResourceItem,bool> filter ){
//var t = db.Table<ResourceItem> ().Where (ri => ri.ItemVersionId == itemVersionId); --* this line is 10 times faster
var t = db.Table<ResourceItem> ().Where (filter); --* this line is 10 times slower
return new List<ResourceItem> (t);
}
I'm not sure because it is xamarin specific, but i suggest to use Expression instead of Func.
Expression<Func<ResourceItem,bool>> filter =
ri => ri.ItemVersioniId == itemVersionId;
public virtual List<ResourceItem> GetResourceItems
(string itemVersionId, Expression<Func<ResourceItem,bool>> filter )
{
return db.Table<ResourceItem> ().Where (filter).ToList();
}
I would advise hard coding it. Here is why, but first let me qualify this by saying I am speculating - I have no experience with SQLiteNet - this based on some general, rudimentary knowledge on how LINQ Prodivers work.
When you have it hard coded the lambda expression is converted to SQL at compile time. When you set it to a delegate, it could be a LINQ to Objects query, there is no way to know at compile time that your LINQ Provider can convert that to a SQL statement. Instead this work occurs at runtime and as a result the performance suffers greatly.

How does C# lambda work?

I'm trying to implement method Find that searches the database.
I forgot to mention that I'm using Postgresql, so I can't use built in LINQ to SQL.
I want it to be like that:
var user = User.Find(a => a.LastName == "Brown");
Like it's done in List class. But when I go to List's source code (thanks, Reflector), I see this:
public T Find(Predicate<T> match)
{
if (match == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
for (int i = 0; i < this._size; i++)
{
if (match(this._items[i]))
{
return this._items[i];
}
}
return default(T);
}
How can I implement this thing? I need to get those parameters to make the search.
Solution
Okay, I understood now that I need to do LINQ to SQL to do all this good expressions stuff, otherwise I'd have to spend a lot of time reimplementeing the wheel.
Since I can't use LINQ to SQL, I implemented this easy method:
public static User Find(User match, string orderBy = "")
{
string query = "";
if (!String.IsNullOrEmpty(match.FirstName)) query += "first_name='" + match.FirstName + "'";
if (!String.IsNullOrEmpty(match.LastName)) query += "last_name='" + match.LastName+ "'";
return Find(query + (!String.IsNullOrEmpty(orderBy) ? orderBy : ""));
}
This is how to use it:
var user = User.Find(new User { FirstName = "Bob", LastName = "Brown" });
Your method should accept Expression<Func<User>>.
This will give you expression tree instead of delegate which you can analyze and serialize to SQL or convert to any other API call your database have.
If you want everything to be generic, you may wish to go on with implementing IQueryable interface. Useful information can be found here: LINQ Tips: Implementing IQueryable Provider
Although for a simple scenario I would suggest not to complicate everything and stick with using Expression Trees and returning plain IEnumerable<T> or even List<T>.
For your case first version of code could look like this:
public IEnumerable<T> Get(Expression<Func<T, bool>> condition)
{
if (condition.Body.NodeType == ExpressionType.Equal)
{
var equalityExpression = ((BinaryExpression)condition.Body);
var column = ((MemberExpression)equalityExpression.Left).Member.Name;
var value = ((ConstantExpression)equalityExpression.Right).Value;
var table = typeof(T).Name;
var sql = string.Format("select * from {0} where {1} = '{2}'", table, column, value);
return ExecuteSelect(sql);
}
return Enumerable.Empty<T>();
}
And it's complexity grows fast when you want to handle new and new scenarios so make sure you have reliable unit tests for each scenario.
C# Samples for Visual Studio 2008 contain ExpressionTreeVisualizer that will help you to dig into Expression Trees more easily to understand how to extract information you need from it.
And of course, if you can stick with using existing implementation of LINQ, I would suggest to do it. There are Linq to SQL for SQL Server databases, Linq to Entities for many different databases, Linq to NHibernate for NHbernate projects.
Many other LINQ providers can be found here: Link to Everything: A List of LINQ Providers. Amount of work to implement LINQ provider is not trivial so it's a good idea to reuse tested and supported solution.
Exactly the same way. Just replace this._items with your users collection.
Also replace the type parameter T with the type User.
A lambda expression in source code can be converted to either a compiled executable delegate or an expression tree upon compilation. Usually we associate lambda's with delegates but in your case since you say you want access to the parameters (in this case I assume you mean LastName and "Brown" then you want an expression tree.
Once you have an expression tree, you can parse it to see exactly what it is an translate it to whatever you actually need to do.
Here are a few questions about expression trees.
Expression trees for dummies?
Bit Curious to understand Expression Tree in .NET
Sounds like you're definitely reinventing a very complicated wheel though. I'm sure it'll be a useful learning experience, but you should look into LINQ to Entities or LINQ to SQL for real-world programming.
Maybe I just haven't understood the question, but there's already a method for doing what you want: Enumerable.Where.
If you need to find a single element then use SingleOrDefault or FirstOrDefault instead.
You could do it something like this:
public static IEnumerable<User> Find(Predicate<User> match)
{
//I'm not sure of the name
using (var cn = new NpgsqlConnection("..your connection string..") )
using (var cmd = new NpgsqlCommand("SELECT * FROM Users", cn))
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
var user = BuildUserObjectFromIDataRecord(rdr);
if (match(user)) yield return user;
}
}
}
And then you can call it like this
var users = User.Find(a => a.LastName == "Brown");
Note that this returns any number of users, you still have to implement the BuildUserObjectFromIDataRecord() function, and that it will always want to iterate over the entire users table. But it gives you the exact semantics you want.
Okay, I understood now that I need to do LINQ to SQL to do all this good expressions stuff, otherwise I'd have to spend a lot of time reimplementeing the wheel.
Since I can't use LINQ to SQL, I implemented this easy method:
public static User Find(User match, string orderBy = "")
{
string query = "";
if (!String.IsNullOrEmpty(match.FirstName)) query += "first_name='" + match.FirstName + "'";
if (!String.IsNullOrEmpty(match.LastName)) query += "last_name='" + match.LastName+ "'";
return Find(query + (!String.IsNullOrEmpty(orderBy) ? orderBy : ""));
}
This is how to use it:
var user = User.Find(new User { FirstName = "Bob", LastName = "Brown" });
One way would be to create an anonymous delegate, like so:
Predicate<User> Finder = delegate(User user)
{
return user.LastName == "Brown";
}
var User = User.Find(Finder);

Convert anonymous type to array or arraylist. Can it be done

Trying to retrieve data using linq from a database. I would like to use anonymous types and convert to an Ilist, Array, ArrayList or Collection. The data is used in a third party object that accepts Ilist, arraylist or collections.
I can't seem to get this to work. I get the following error, "Sequence operators not supported for type 'System.String'"
using (var db = new dbDataContext())
{
var query = from e in db.people
select new
{
Count = e.LastName.Count()
};
Array test;
test = query.ToArray();
}
It's got nothing to do with converting the results to array lists, or even anonymous types. Here's another version of your code which will fail:
using (var db = new dbDataContext())
{
var query = db.people.Select(x => x.LastName.Count());
foreach (int x in query)
{
Console.WriteLine(x);
}
}
That will still fail in the same way - because it's the translation of this bit:
x => x.LastName.Count()
into SQL which is causing problems.
Change it to:
x => x.LastName.Length
and I suspect you'll find it works. Note that this isn't really a C# issue - it's just LINQ to SQL's translation abilities.
I would suggest that you don't use an anonymous type here though - it's pointless. Maybe this isn't your complete code, but in general if you find yourself creating an anonymous type with a single member, ask yourself if it's really doing you any good.
The ArrayList class has a constructor that accepts ICollection.
You should be able to feed it a List version of your LINQ result.
using (var db = new dbDataContext()) {
var query = from e in db.people
select new { Count = e.LastName.Count() };
ArrayList list = new ArrayList(query.ToList());
}
I don't have Visual Studio here (I'm on my Mac), but it might be of help.
(ToArray should suffice as well)
You might need to replace your Count() by Length.

Categories