I'm trying to extend/copy this code by
marisks
to include a default sorting interceptor.
But for the life of me, I cannot wrap my head around the expressions and bindings.
Inspired by the SoftDelete code
var table = (EntityType)expression.Target.ElementType;
if (table.Properties.All(p => p.Name != IsDeletedColumnName))
{
return base.Visit(expression);
}
var binding = expression.Bind();
return binding.Filter(
binding.VariableType
.Variable(binding.VariableName)
.Property(IsDeletedColumnName)
.IsNull()
);
I'm trying to add a similar thing to sort data be default.
This is the closest I've come (that will compile)
var table = (EntityType)expression.Target.ElementType;
string sortingColumn = "Priority";
var binding = expression.Bind();
return binding.Filter(
binding.VariableType
.Variable(binding.VariableName)
.Property(sortingColumn)
.OrderBy(m => m)
);
but it throws an exception at runtime:
DbExpressionBinding requires an input expression with a collection ResultType.
Parameter name: input
Can someone help me fix this - and possibly help me understand what's going on?
I did some hacking on my own. Basically the problem is that you try to create a Filter expression, but you need a Sort expression. Something like this:
var sortingColumn = "OrderProp";
var binding = expression.Bind();
return DbExpressionBuilder.Sort(binding,
new[] {
DbExpressionBuilder.ToSortClause(binding.VariableType.Variable(binding.VariableName).Property(sortingColumn) )
});
Of course, this only works, if you have the "OrderProp" on the type itself (you have to add extra checks for that).
Related
I am trying to use Expression Trees because based on description, that seems to be the most correct (performant, configurable) approach.
I expect to be able to craft a statement that gets the first item from the existingItems collection that matches the propertyNameToCompareOn value of the incomingItem.
I have a method with the following signature and simulated code body...
DetectDifferences<T>(List<T> incomingItems, List<T> existingItems)
{
var propertyNameToCompareOn = GetThisValueFromAConfigFile(T.FullName());
//does this belong outside of the loop?
var leftParam = Expression.Parameter(typeof(T), "left");
var leftProperty = Expression.Property(leftParam, identField);
var rightParam = Expression.Parameter(typeof(T), "right");
var rightProperty = Expression.Property(rightParam, identField);
//this throws the error
var condition = Expression.Lambda<Func<T, bool>>(Expression.Equal(leftProperty, rightProperty));
foreach (var incomingItem in incomingItems) //could be a parallel or something else.
{
// also, where am I supposed to provide incomingItem to this statement?
var existingItem = existingItems.FirstOrDefault(expression/condition/idk);
// the statement for Foo would be something like
var existingFoos = exsistingItems.FirstOrDefault(f => f.Bar.Equals(incomingItem.Bar);
//if item does not exist, consider it new for persistence
//if item does exist, compare a configured list of the remaining properties between the
// objects. If they are all the same, report no changes. If any
// important property is different, capture the differences for
// persistence. (This is where precalculating hashes seems like the
// wrong approach due to expense.)
}
}
At the marked line above, I get an "Incorrect number of parameters supplied for lambda declaration" InvalidOperationException. At this point I am just hacking crap together from the web and I really dont know what this wants. There are a bunch of overloads that VS can full my screen with, and none of the examples make sense from the articles on MSDN/SO.
PS - I dont really want an IComparer or similar implementation if it can be helped. I can do that with reflection. I do need to make this as rapid as possible, but allow it to be called for multiple types, hence the choice of expression trees.
When working with expression trees, it's important to first understand, in real code, what you want to do.
I always begin by first writing out (in static code) what the resulting expression looks like with real C# lambda syntax.
Based on your description, your stated goal is that you should be able to (dynamically) look up some property of the type T that gives some sort of quick comparison. How would you write this if both T and TProperty were both known at compile time?
I suspect it would look something like this:
Func<Foo, Foo, bool> comparer = (Foo first, Foo second) =>
first.FooProperty == second.FooProperty;
Right away we can see that your Expression is wrong. You don't need one input T, you need two!
It should also be obvious why you're getting the InvalidOperationException as well. You never supplied any parameters to your lambda expression, only the body. Above, 'first' and 'second' are the parameters provided to the lambda. You'll need to provide them to the Expression.Lambda()call as well.
var condition = Expression.Lambda<Func<T,T, bool>>(
Expression.Equal(leftProperty, rightProperty),
leftParam,
rightParam);
This simply uses the Expression.Lambda(Expression, ParameterExpression[]) overload for Expression.Lambda. Each ParameterExpression is the parameter that is used in the body. That's it. Don't forget to .Compile() your expression into a delegate if you want to actually invoke it.
Of course this doesn't mean that your technique will be necessarily fast. If you're using fancy expression trees to compare two lists with a naive O(n^2) approach, it won't matter.
Here's a method to make a property access expression;
public static Expression<Func<T, object>> MakeLambda<T>(string propertyName)
{
var param = Expression.Parameter(typeof(T));
var propertyInfo = typeof(T).GetProperty(propertyName);
var expr = Expression.MakeMemberAccess(param, propertyInfo);
var lambda = Expression.Lambda<Func<T, object>>(expr, param);
return lambda;
}
which you can use like this;
var accessor = MakeLambda<Foo>("Name").Compile();
accessor(myFooInstance); // returns name
Making your missing line
var existingItem = existingItems.FirstOrDefault(e => accessor(e) == accessor(incomingItem));
Be aware the == only works well for value types like ints; careful of comparing objects.
Here's proof the lambda approach is much faster;
static void Main(string[] args)
{
var l1 = new List<Foo> { };
for(var i = 0; i < 10000000; i++)
{
l1.Add(new Foo { Name = "x" + i.ToString() });
}
var propertyName = nameof(Foo.Name);
var lambda = MakeLambda<Foo>(propertyName);
var f = lambda.Compile();
var propertyInfo = typeof(Foo).GetProperty(nameof(Foo.Name));
var sw1 = Stopwatch.StartNew();
foreach (var item in l1)
{
var value = f(item);
}
sw1.Stop();
var sw2 = Stopwatch.StartNew();
foreach (var item in l1)
{
var value = propertyInfo.GetValue(item);
}
sw2.Stop();
Console.WriteLine($"{sw1.ElapsedMilliseconds} vs {sw2.ElapsedMilliseconds}");
}
As someone's also pointed out, though, the double-loop in the OP is O(N^2) and that should probably be the next consideration if efficiency is the driver here.
I would like to make my code convention-based by using Types and keeping things simple, but generics has it's own complexity with it's own learning curve.
I have a bunch of POCOs (Plain Old CLR Objects) in a List that I'd like to iterate through later in the code.
var models = new List<Type>();
models.Add(typeof(Person));
models.Add(typeof(Company));
Would like to cycle through each list item:
models.ForEach(m =>
{
var label = m.FullName;
// var data = JsonConvert.DeserializeObject<List<typeof(m)>>(""); // doesn't work
var data = JsonConvert.DeserializeObject<List<m>>(""); // doesn't work either
...
}
The issue is that the "m" in the Deserialize line isn't working. What would be the best way to pass that through, i.e. making the 'List<m>' a 'List<T>' that we can use?
To use generics, you really need to know the Type (T) at compile time, you don't - you know it at run time. (Caveat: Its possible with reflection, but theres no need to use it when there's an overload as described below)
There is an overload of DeserializeObject which takes a Type rather than use generics. So your code would be
models.ForEach(m =>
{
var label = m.FullName;
var data = JsonConvert.DeserializeObject("",m);
...
}
However, as you've pointed out in comments you actually need a List<T> not a single T. You'll need a little bit of reflection, just to create the right type to pass to the above DeserializeObject call.
var tList = typeof(List<>); // Type of open List
models.ForEach(m =>
{
var label = m.FullName;
var tConvert = = tList.MakeGenericType(m);
var data = JsonConvert.DeserializeObject("",tConvert);
...
}
The answer to your question is above, but the more I look at it the harder it is to see what you can actually do with data. all you'll ever know about data is that it is an object. You cant cast it to anything - you wont know if its a list of Person or a list of Company.
Perhaps this was an overly contrived example you've used for a real-life problem. If not I forsee you're next problem is what to do with data!!
If you don't know the type at compile time you can do this with Reflection. Consider the following code:
models.ForEach(m =>
{
var mi = JsonConvert.GetType()
.GetMethod("DeserializeObject");
var m = mi.MakeGenericMethod(new[] { m });
// you pass [null] for the object because it's a [static] method
// and you don't have to declare [args] if you can express it simply
// but keep in mind that it's simply an object[]
m.Invoke(null, args);
}
Another solution is to call the generic method using reflection (if there isn't any overload that takes the type as parameter)
models.ForEach(m =>
{
MethodInfo method = typeof(JsonConvert).GetMethod("DeserializeObject");
MethodInfo generic = method.MakeGenericMethod(m);
generic.Invoke(null, "");
}
I want one method that can query my entire RavenDB database.
My method signature looks like this:
public static DataTable GetData(string className, int amount, string orderByProperty, string filterByProperty, string filterByOperator, string filterCompare)
I figured I can accomplish all of the above with a dynamic LuceneQuery.
session.Advanced.LuceneQuery<dynamic>();
The problem is: Since I'm using dynamic in the type given, how do I ensure that the query only includes the types matching the className?
I'm looking for something like .WhereType(className) or .Where("type: " + className).
Solution
This returns the results of the correct type:
var type = Type.GetType("Business.Data.DTO." + className);
var tagName = RavenDb.GetTypeTagName(type);
using (var session = RavenDb.OpenSession())
{
var result = session.Advanced
.LuceneQuery<object, RavenDocumentsByEntityName>()
.WhereEquals("Tag", tagName)
.ToList();
}
Note, it is not possible to add additional "WhereEquals" or other filters to this. This is because nothing specific to that document type is included in the "RavenDocumentByEntityName" index.
This means that this solution cannot be used for what I wanted to accomplish.
What I ended up doing
Although it doesn't fulfill my requirement completely, this is what I ended up doing:
public static List<T> GetData<T>(DataQuery query)
{
using (var session = RavenDb.OpenSession())
{
var result = session.Advanced.LuceneQuery<T>();
if (!string.IsNullOrEmpty(query.FilterByProperty))
{
if (query.FilterByOperator == "=")
{
result = result.WhereEquals(query.FilterByProperty, query.FilterCompare);
}
else if (query.FilterByOperator == "StartsWith")
{
result = result.WhereStartsWith(query.FilterByProperty, query.FilterCompare);
}
else if (query.FilterByOperator == "EndsWith")
{
result = result.WhereEndsWith(query.FilterByProperty, query.FilterCompare);
}
}
if (!string.IsNullOrEmpty(query.OrderByProperty))
{
if (query.Descending)
{
result = result.OrderBy(query.OrderByProperty);
}
else
{
result = result.OrderByDescending(query.OrderByProperty);
}
}
result = result.Skip(query.Skip).Take(query.Amount);
return result.ToList();
}
}
Although this is most certainly an anti-pattern, it's a neat way to just look at some data, if that's what you want. It's called very easily like this:
DataQuery query = new DataQuery
{
Amount = int.Parse(txtAmount.Text),
Skip = 0,
FilterByProperty = ddlFilterBy.SelectedValue,
FilterByOperator = ddlOperator.SelectedValue,
FilterCompare = txtCompare.Text,
OrderByProperty = ddlOrderBy.SelectedValue,
Descending = chkDescending.Checked
};
grdData.DataSource = DataService.GetData<Server>(query);
grdData.DataBind();
"Server" is one of the classes/document types I'm working with, so the downside, where it isn't completely dynamic, is that I would have to define a call like that for each type.
I strongly suggest you don't go down this road. You are essentially attempting to hide the RavenDB Session object, which is very powerful and intended to be used directly.
Just looking at the signature of the method you want to create, the parameters are all very restrictive and make a lot of assumptions that might not be true for the data you're working on. And the return type - why would you return a DataTable? Maybe return an object or a dynamic, but nothing in Raven is structured in tables, so DataTable is a bad idea.
To answer the specific question, the type name comes from the Raven-Entity-Name metadata, which you would need to build an index over. This happens automatically when you index using the from docs.YourEntity syntax in an index. Raven does this behind the scenes when you use a dynamic index such as .Query<YourEntity> or .Advanced.LuceneQuery<YourEntity>.
Still, you shouldn't do this.
I have a rather complex database, where we are using Linq to SQL. I'm creating a model layer where I would like to only have one method. But my problem is that we often like to order the collection. Is it possible somehow to accomplish something like this:
public static List<Object> GetObject(Object.Property)
{
return some Linq.ToList();
}
I know I can use linq on my list afterwards.
Hmm it was maybe a bit to diffuse question.
OK I solved it with reflection and a string in the argument..
MyObjectDataDataContext context = new MyObjectDataDataContext ();
PropertyInfo[] piArray = context.MyObject.GetType().GetProperties();
PropertyInfo pi = piArray.FirstOrDefault(s => s.Name == "property");
if (pi != null)
{
return context.MyObject.OrderBy(t => pi.PropertyType);
}
I guess you are trying to access the same data, but depending on a 'column' criteria, return the data sorted?
Once you have the IEnumerable data, you can sort it as follows:
list.OrderBy(t => t.ColumnYouWantToSortBy)
As in the following documentation
I'm trying to build a method that will receive a Linq table, and should return a List<> of values that will be a DropDownList Datasource.
This is what I've got till now:
public static List<Structs.NameValuePair> GenDropDownItens<T>(string ValueField , string TextField ) where T: class
What i don't know how to do is, query the table getting only the fields that are passed ( ValueField, TextField)...
Tks!
Project the result of your LINQ2SQL query into a System.Collections.Generic.KeyValuePair object like so:
ddl.DataSource = DataContext.Table.Select(o => new KeyValuePair<string, string>(o.ID, o.DisplayField));
ddl.DataBind();
You will then want to set the DataValueField and DataTextField attributes on the DropDownList to "Key" and "Value" respectively.
Why not just do something like;
var dropDownValues = dataContext.SomeTable.ToDictionary(
s => s.Name,
s => s.Value
);
foreach(var item in dropDownValues) {
var OptionName = item.Key;
var OptionValue = item.Value
};
Hope this helps, I really don't think you need to create a while method. But if you wanted to I would say have it take a IDictionary object, and convert it from there.
Table.Select( t => t.field1, t.field2 )
Also check out Scott Gutherie's blog series here.
Are you trying to do something like the following with your method
GetDropDownItems("Gates", "LastName") ????
If so, included as part of the SDK samples is a project called DynamicQuery. Using this you can basically create a text version of the query you want. You could do something like
"LastName == 'Gates'"
However, it is just as easy to build the expression tree yourself. The best way to learn what the expressions tree's look like is to use the ExpressionTreeVisualizer VS debugger add in (note this is also another sample contained in the SDK CSharpSamples). It would be something like
ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
var expression = Expression.Equals(Expression.Property(parameter, "LastName"), Expression.Constant("Gates")
If "Key" and "Value" are strings that represent the name of the properties you would like to get, and they are known only at runtime... here's your code:
private static Func<T, DictionaryEntry> GetNameValuePairFunc<T>(string valueField, string textField)
{
Func<T, DictionaryEntry> result = (item) =>
{
object key = typeof(T).GetProperty(valueField).GetValue(item, null);
object text = typeof(T).GetProperty(textField).GetValue(item, null);
return new DictionaryEntry(key, text);
};
return result;
}