Passing expression into lambda - c#

I have such class:
public class SomeClass
{
public string Text1 { get; set; }
public string Text2 { get; set; }
public int Number { get; set; }
}
And I have list of this classes objects:
List<SomeClass> myList = new List<SomeClass>();
I want to query this list using LINQ (lambda syntax):
var result = myList.Where(obj => obj.Text1 == "SomeString");
Is there any way to pass property(eg. by string name), by which I want this LINQ query to be performed? In this example, I search by Text1 property, but let's say I want to invoke this search dynamically on Text1 or Text2 (determined in runtime). I want to be able to pass property name, on which this search is performed, and check whether this property is string, so that I'm sure this search CAN be performed first.
Is that possible? I know Reflections and Expressions have something to do about it, but I don't know them very well.
Thanks

The approach using Reflection:
var result = myList.Where(obj => obj.GetType()
.GetProperty("Text1")
.GetValue(obj)
.Equals("SomeString"));
With this way you can change from "Text1" to "Text2" property.
Another approach you can use dynamic linq:
var result = myList.AsQueryable().Where("Text1=#0", "SomeString");
Dynamic LINQ is also available via nuget.

You could use expression-trees?
string memberName = "Text1", value = "SomeString";
var p = Expression.Parameter(typeof(SomeClass), "obj");
var predicate = Expression.Lambda<Func<SomeClass, bool>>(
Expression.Equal(
Expression.PropertyOrField(p, memberName),
Expression.Constant(value,typeof(string))
), p);
var result = myList.AsQueryable().Where(predicate);
or alternative for the last line:
var result = myList.Where(predicate.Compile());

Related

How to retrieve object properties with LINQ?

Let's say I have this object:
public class Foo
{
public string FirstProp {get;set;}
public string SecondProp {get;set;}
public string ThirdProp {get;set;}
}
Now I would like to retrieve only the FirstProp and the SecondProp from that object and concat all the property values into one string.
I have one solution in mind which would't be clean imo. Here it is:
var foo = new Foo("test1","test2","test3");
var propertyNames = new[] {"FirstProp", "SecondProp"};
var properties = foo.GetType().GetProperties().Where(x => propertyNames.Contains(x.Name));
//Then loop through each retrieved property and concat the string
So basically I am just looking for a cleaner solution where I wouldn't be dependent on an array of string.
var foo = new Foo(...);
var result = string.Join(",", new[]{ foo.FirstProp, foo.SecondProp });
Does this suffice? Or do you need Reflection for dynamic typing? If so, one can also supply MemberExpressions to dynamically get the values. Are you dealing with a collection of instances? Do you need this functionality extracted in a helper-method with a parameter for the needed properties?

Given this lambda, how can I write it manually with expression trees?

class Program
{
static void Main(string[] args)
{
Expression<Func<string[], Poco>> exp = a => new Poco { MyProperty1 = a[0], MyProperty2 = a[1], MyProperty3 = a[2] };
var lambda = exp.Compile();
var output = lambda(new[] {"one", "two", "three"});
Console.WriteLine(output.MyProperty1);
}
}
class Poco
{
public string MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
public string MyProperty3 { get; set; }
}
I'm not interested in the part calling the lambda, thats just for completeness. I get completely lost trying to navigate expression trees, and this might teach me how to fish.
private static Expression<Func<string[], Poco>> CreateExpr()
{
ParameterExpression paramExpr = Expression.Parameter(typeof(string[]), "a");
var newExpr = Expression.New(typeof(Poco));
var memberExprs = Enumerable.Range(0, 3)
.Select(i =>
{
string propertyName = "MyProperty" + (i + 1);
var property = typeof(Poco).GetProperty(propertyName);
Expression.Bind(property, Expression.ArrayIndex(paramExpr, Expression.Constant(i)));
});
var expr = Expression.MemberInit(newExpr, memberExprs);
return Expression.Lambda<Func<string[], Poco>>(expr, paramExpr);
}
I don't have time right now to translate the complete tree, but one thing you can do is compile your code and then use ildasm (or reflector etc) to look at what the compiler's doing. You can't always do exactly the same in your own code, but it gives you an idea of the kind of expressions you'll want. In particular, in this case you'll want:
Expression.Parameter to create the parameter (a)
Expression.New to create the new instance
Expression.Bind to create a property assignment
Expression.MemberInit to assign the properties in the new object
Expression.ArrayIndex for each array access (a[0] etc)
Expression.Constant for the array indexes themselves (0, 1, 2)
Expression.Lambda to create an Expression<TDelegate> for the whole thing
If I get time later on, I'll try to construct a complete working example.

Exclude a collection from another by lambda

This is my type:
public class myType
{
public int Id { get; set; }
public string name { get; set; }
}
And there is 2 collection of this type:
List<myType> FristList= //fill ;
List<myType> Excludelist= //fill;
And I need to exclude Excludelist from FristList something like the following:
List<myType> targetList =
FirstList.Where(m=>m.Id not in (Excludelist.Select(t=>t.Id));
What is your suggestion about the exact lambda expression of the above query?
Three options. One without any changes:
var excludeIds = new HashSet<int>(excludeList.Select(x => x.Id));
var targetList = firstList.Where(x => !excludeIds.Contains(x.Id)).ToList();
Alternatively, either override Equals and GetHashCode and use:
var targetList = firstList.Except(excludeList).ToList();
Or write an IEqualityComparer<MyType> which compares by IDs, and use:
var targetList = firstList.Except(excludeList, comparer).ToList();
The second and third options are definitely nicer IMO, particularly if you need to do this sort of work in various places.

OrderBy with a String keySelector

I have the following function that is extracting me distinct values based on the properties of an object, here Client.
public List<DistinctValue> GetDistinctValues(string propertyName)
{
//how should I specify the keySelector ?
Func<string, object> keySelector = item => propertyName;
var list = new List<DistinctValue>();
var values = this.ObjectContext.Clients.Select(CreateSelectorExpression
(propertyName)).Distinct().OrderBy(keySelector);
int i = 0;
foreach (var value in values)
{
list.Add(new DistinctValue() { ID = i, Value = value });
i++;
}
return list;
}
private static Expression<Func<Client, string>> CreateSelectorExpression
(string propertyName)
{
var paramterExpression = Expression.Parameter(typeof(Client));
return (Expression<Func<Client, string>>)Expression.Lambda(
Expression.PropertyOrField(paramterExpression, propertyName),
paramterExpression);
}
public class DistinctValue
{
[Key]
public int ID { get; set; }
public string Value { get; set; }
}
I'm doing this because I do not know in before which property values I'll need to extract.
It's working, just the result is not sorted.
Can you please help me correct the sorting to make the OrderBy work as expected?
The properties are strings and I don't need to chain the sorting. I don't need to specify the sorting order either.
Thanks a lot in advance,
John.
Your keySelector currently returns the same string for each (the property name); and since LINQ is typically a stable sort, this results in no overall change. Since you have already projected to the string values, you can simply use a trivial x=>x mapping here:
var values = this.ObjectContext.Clients.Select(
CreateSelectorExpression(propertyName)).Distinct().OrderBy(x => x);
to order by the items themselves.
Thanks for the elegant solution. I further expanded upon the CreateSelectorExpression method so it can be leveraged outside of the Client class in the example above.
public static Expression<Func<T, string>> CreateSelectorExpression<T>(string propertyName)
{
var paramterExpression = Expression.Parameter(typeof(T));
return (Expression<Func<T, string>>)Expression.Lambda(Expression.PropertyOrField(paramterExpression, propertyName),
paramterExpression);
}
Usage
Func<IQueryable<YourEntity>, IOrderedQueryable<YourEntity>> orderBy = o => o.OrderByDescending(CreateSelectorExpression<YourEntity>("Entity Property Name"));

C# Dynamically apply evaluation rule on a collection

I have a fixed-length data file need to persistence into database. I use a XML file to define the length for the fields and use a list of FieldItem class to store the data.
class FieldItem
{
public string ObjectName {get; set;}
public string ObjectProperty {get; set;}
public string ObjectValue {get; set;}
public FieldItem()
{
}
}
So the FieldItem will look like
var fieldItem = new FieldItem
{
ObjectName = "Company",
ObjectProperty = "Name",
ObjectValue = "ABC Corp."
}
After get the list of FieldItem, I will do refection to create Company and other domain objects to save them into database.
But before they are saved into database, I have some business rules needed to be applied to validate the data line. My data line will look like:
var fieldItemList = new List<FieldItem>(){
new FieldItem {
ObjectName="Company",
ObjectProperty = "Name",
ObjectValue = "ABC"
}
new FieldItem{
ObjectName = "Product",
ObjectProperty = "ProductCode",
ObjectValue ="XYZ0123"
new FieldItem{
ObjectName = "Product",
ObjectProperty = "ProductName",
ObjectValue ="Christmas Tree"
}
// other FieldItem objects...
}
For example, my rule is to check if the company == "ABC" and ProductCode == "XYZ0123". Those rules are created by users and stored as a string in the database. What I am doing right now is to use Microsoft's System.Linq.Dynamic to evaluate the rule, such as
string validationRule = " (ObjectName == \"Company\" And ObjectProperty=\"CompanyName\" And ObjectValue = \"ABC Corp.\") And (ObjectName == \"Product\" And ObjectProperty=\"ProductCode\" And ObjectValue = \"XYZ0123\") ";
var query = fieldItemList.AsQuerable().Where(validationRule);
then check if it has one row return to tell if that data row has passed the rule or not.
Obviously, it is too verbose. Do you have any better suggestion? What should I do if I only like my rule expression like: "Company.CompanyName = 'ABC Corp.' and Product.ProductCode = 'XYZ001'"?
RE: "What should I do if I only like my rule expression like: "Company.CompanyName = 'ABC Corp' and Product.ProductCode = 'XYZ001'"?
Map this user-friendly query: "Company.CompanyName = 'ABC Corp' AND Product.ProductCode = 'XYZ001'" to something that is friendly to your data structure(FieldItem):
"ObjectName = 'Company' AND ObjectProperty = 'CompanyName' AND ObjectValue = 'ABC Corp' OR ObjectName = 'Product' AND ObjectProperty = 'ProductCode' AND ObjectValue = 'XYZ001'"
How to know if the user's rules passed the rules or not? Count the number of conditions, if it matches the count of results of fieldItemList.AsQueryable().Where(itemFieldFriendlyQuery), then the data line is valid based on user's rules.
some rudimentary mapper(use regular expression or roll your own parser to make the following code truly valid):
public Form3()
{
InitializeComponent();
string userFriendlyQuery = "Company.CompanyName = 'ABC Corp' AND Product.ProductCode = 'XYZ001'";
string[] queryConditions = userFriendlyQuery.Split(new string[]{" AND "},StringSplitOptions.None);
int conditionsCount = queryConditions.Length;
string itemFieldFriendlyQuery = string.Join(" OR ",
queryConditions.Select(condition =>
{
var conditionA = condition.Split(new string[] { " = " }, StringSplitOptions.None);
var left = conditionA[0];
var leftA = left.Split('.');
string objectName = leftA[0];
string objectProperty = leftA[1];
var right = conditionA[1];
return string.Format("ObjectName = '{0}' AND ObjectProperty = '{1}' AND ObjectValue = {2}",
objectName, objectProperty, right);
}
).ToArray());
MessageBox.Show(itemFieldFriendlyQuery);
// outputs: "ObjectName = 'Company' AND ObjectProperty = 'CompanyName' AND ObjectValue = 'ABC Corp' OR ObjectName = 'Product' AND ObjectProperty = 'ProductCode' AND ObjectValue = 'XYZ001'"
bool isValid = fieldItemList.AsQueryable().Where(itemFieldFriendlyQuery).Count() == conditionsCount;
MessageBox.Show(isValid.ToString());
}
Consider using the FileHelpers library to parse your file directly into regular .NET domain objects. I believe this, combined with your Dynamic Linq approach, will provide you with the syntax that you're looking for.
You could have them store it in the database as C# code expression, and then use the CodeDom classes to parse and compile it into a type which exposes a method which will run the comparison.
That will create a lot of temp assemblies, which would be a PITA to manage, IMO.
Rather, I'd still use the C# notation for expression syntax, but instead of compiling it into code, create a lambda expression dynamically (through the Expression class) and then compile into a delegate that takes all the parameters (you will need a mapping mechanism here) and then just call it using your created types.
As an aside: why class instead of struct?
Value types for data, reference types for behaviour.
For your FieldItem class, couldn't you create a property where you can inject a Predicate delegate method, such that:
class FieldItem
{
public string ObjectName {get; set;}
public string ObjectProperty {get; set;}
public string ObjectValue {get; set;}
public Predicate<FieldItem> EvalMethod { private get; set; }
public FieldItem()
{
}
public bool Evaluate()
{
return EvalMethod(this);
}
}
For constructing your FieldItem object then, you could use the following:
new FieldItem { ObjectName = "SomeObject",
ObjectProperty = "SomeProperty",
ObjectValue = "SomeValue",
EvalMethod = GetEvalMethodFor("SomeObject")
}
Of course you will have to have a parser of some sort (GetEvalMethodFor method above) to translate your validationRule string into a boolean evaluation method.
In your collection, you can then call the Evaluate() within, say, a foreach loop.
UPDATE
If you need to evaluate each and every object in the list, you can do this:
bool areAllItemsValid = true;
foreach (var f in fieldItemList)
{
areAllItemsValid = areAllItemsValid && f.Evaluate();
if (areAllItemsValid)
{
continue;
}
else
{
return false;
}
}
return true;
You can opt to inherit List<FieldItem> and make an EvaluateAll() method to contain the above, or declare it as a method in a separate object altogether.

Categories