Multiple condition using Lambda Expression in C# - c#

The following code works for me if I need to filter a generic query for a single parameter, like Status=1.
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query)
{
var propertyName = "Status";
var param_1 = "1";
//var param_2 = 2;
//var param_3 = "DE";
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
var converter = TypeDescriptor.GetConverter(propertyExp.Type);
var result = converter.ConvertFrom(param_1);
var value = Expression.Constant(result);
var equalBinaryExp = Expression.Equal(propertyExp, value);
query = query.Where(Expression.Lambda<Func<T, bool>>(equalBinaryExp, parameterExp));
return query;
}
This is like filtering a list with a value.
var list = new List<Employee> {
new Employee { Id = 1, Name = "Phil", Status=1, Country="DE" } ,
new Employee { Id = 2, Name = "Kristina", Status=2, Country="DE" },
new Employee { Id = 3, Name = "Mia", Status=1, Country="US" }
};
list = list.Where(x => (x.Status == 1)).ToList();
But how should I modify the method FilterBy, so that it will work for filtering multiple values? Like (Status=1 or Status=2) and Country="DE",
list = list.Where(x => (x.Status == 1 || x.Status == 2) && x.Country == "DE").ToList();
Thanks for your time.

The trick when writing lambdas by hand is to write them how you would in regular C#, and decompile them, perhaps using sharplab like this. If you look on the right, you can see that it is doing something like:
Expression.AndAlso(
Expression.OrElse(
Expression.Equal(idPropExp, Expression.Constant(1)),
Expression.Equal(idPropExp, Expression.Constant(2))
),
Expression.Equal(countryPropExp, Expression.Constant("DE"))
)

Related

Dynamic Linq statement not working in EF core

Working on a asp net core 2.2 application. I want to dynamic order a query result.
this is the code I have:
public IActionResult OnGetRecords(int pagenum, int pagesize, string sortDataField, string sortOrder)
{
sortOrder = sortOrder ?? "asc";
var Mut = from M in _DB.Mutations
join S in _DB.Shifts on M.ShiftId equals S.ShiftId
join U in _DB.RoosterUsers on M.UserId equals U.RoosterUserId
select new MutationModel
{
MutId=M.MutationId,
Naam=U.FirstName + " " + U.LastName,
UserId=M.UserId,
MutationType =S.publicName,
DateVan=M.DateStartOn,
DateTot=M.DateTill
};
if (sortDataField != null)
{
if (sortOrder == "asc")
{
Mut = Mut.OrderBy(m => m.GetType().GetProperty(sortDataField).GetValue(m, null));
}
else
{
Mut = Mut.OrderByDescending(m => m.GetType().GetProperty(sortDataField).GetValue(m, null));
}
}
int total = Mut.Count();
var Tresult = Mut.Skip(pagenum * pagesize).Take(pagesize);
var uit = new
{
TotalRows = total,
Rows = Tresult
};
return new JsonResult(uit);
}
}
But it is not working, when I try to order on a field, the line:
Mut = Mut.OrderBy(m => m.GetType().GetProperty(sortDataField).GetValue(m, null));
is not giving an error but is returning an result with out records.
Is EF core different from the 'old' EF in this?
somebody knows how to do this in EF Core
m => m.GetType().GetProperty(sortDataField).GetValue(m, null)
Is not a valid expression for OrderByin this case that can be translated into valid SQL for EF to execute
You will need to use the sortDataField to build an expression dynamically to use with the OrderBy calls.
The following is done as an extension method for convenience
public static Expression<Func<TModel, object>> GetPropertyExpression<TModel>(this IEnumerable<TModel> model, string propertyName) {
// Manually build the expression tree for
// the lambda expression m => m.PropertyName.
// (TModel m) =>
var parameter = Expression.Parameter(typeof(TModel), "m");
// (TModel m) => m.PropertyName
var property = Expression.PropertyOrField(parameter, propertyName);
// (TModel m) => (object) m.PropertyName
var cast = Expression.Convert(property, typeof(object));
var expression = Expression.Lambda<Func<TModel, object>>(cast, parameter);
return expression;
}
It builds up the expression tree for sorting that can then be used like
if (sortDataField != null) {
//m => m.sortDataField
var keySelector = Mut.GetPropertyExpression(sortDataField);
if (sortOrder == "asc") {
Mut = Mut.OrderBy(keySelector);
} else {
Mut = Mut.OrderByDescending(keySelector);
}
}
to order the query
Sorry to be late with this. Thanks for your help. But the problem was something different. In Asp.net.core you send the result via return new JsonResult(uit); to the client.
The problem is that capitalized field names are changed to non capitalized names. so If you send them back to the server again with an Ajax call you have to Capitalize them again!

Cannot convert Linq to SQL

So for a assignement I got I have to convert the data I get through an API into a object and save that object in the database. Since the object doesn't have a any property that is unique there are multiple primary key columns in my database.
So what I basically have to do is a 'merge' from to my database so if data has been changed to a update if data is new do an insert. For some reason one my linq queries for this is giving an error.
My database:
My Code:
public void Insert(List<MEDSU1> medsu1s)
{
var keys = medsu1s.Select(y => new { SUPP = y.S1SUPP?.Trim() ?? null, COMP = y.S1COMP?.Trim() ??null }).ToList();
List<MEDSU1> medusFromSql = MediusDataContext.MEDSU1s.Where(y =>
keys.Any(z => z.SUPP == y.S1SUPP && z.COMP == y.S1COMP)).ToList();
var toUpdate = from medsu1org in MediusDataContext.MEDSU1s
join sqlkeys in medusFromSql
on new
{
aa = medsu1org.S1COMP,
bb = medsu1org.S1SUPP
}
equals
new
{
aa = sqlkeys.S1COMP,
bb = sqlkeys.S1SUPP
}
select new
{
sql = medsu1org,
obj = sqlkeys,
};
toUpdate.ToList().ForEach(y =>
{
y.obj.S1COMP= y.sql.S1COMP;
y.obj.S1SUPP = y.sql.S1SUPP;
y.obj.S1SUNA = y.sql.S1SUNA;
y.obj.S1BAAC = y.sql.S1BAAC;
y.obj.S1VATN = y.sql.S1VATN;
y.obj.S1COUN = y.sql.S1COUN;
y.obj.S1PREF = y.sql.S1PREF;
y.obj.S1TAXD = y.sql.S1TAXD;
y.obj.S1CURR = y.sql.S1CURR;
y.obj.S1TYPE = y.sql.S1TYPE;
y.obj.S1ACNR = y.sql.S1ACNR;
y.obj.S1ACNM = y.sql.S1ACNM;
y.obj.S1EINV = y.sql.S1EINV;
y.obj.S1DLAY = y.sql.S1DLAY;
y.obj.S1TERM = y.sql.S1TERM;
y.obj.S1PYEE = y.sql.S1PYEE;
});
var toInsert = medsu1s.Except(toUpdate.Select(y => y.obj)).ToList();
MediusDataContext.MEDSU1s.InsertAllOnSubmit(toInsert);
MediusDataContext.SubmitChanges();
}
The part of the code that is giving me the error is the:
var keys = medsu1s.Select(y => new { SUPP = y.S1SUPP?.Trim() ?? null, COMP = y.S1COMP?.Trim() ??null }).ToList();
List<MEDSU1> medusFromSql = MediusDataContext.MEDSU1s.Where(y =>
keys.Any(z => z.SUPP == y.S1SUPP && z.COMP == y.S1COMP)).ToList();
I think it has to do with me using .Any incorrectly and converting it to a List but i dont know to fix it. Can someone explain me what I'm doing wrong?
ERROR im getting : "Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator."
Since you're dealing with a compound key you'll have to create a query that basically looks like
Select *
From MEDSU1s
Where (S1SUPP == #S1SUPP1 && S1COMP == #S1COMP1)
OR (S1SUPP == #S1SUPP2 && S1COMP == #S1COMP2)
OR ....
To do that with Linq you'd have to build the expression procedurally. Note that I'm assuming the columns are both strings, so change the type of them as needed. Also I have no good way of testing this out but hopefully this can get you started towards a solution.
var queryableData = MediusDataContext.MEDSU1s;
var table = Expression.Parameter(typeof(MEDSU1s), "x");
var suppCol = Expression.Property(table, typeof(string), "S1SUPP");
var compCol = Expression.Property(table, typeof(string), "S1COMP");
Expression condition = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
foreach (var x in keys)
{
var key1 = Expression.Equal(suppCol, Expression.Constant(x.SUPP));
var key2 = Expression.Equal(compCol, Expression.Constant(x.COMP));
var both = Expression.AndAlso(key1, key2);
condition = Expression.OrElse(condition, both);
}
var whereExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<MEDSU1s, bool>>(
condition,
new ParameterExpression[] { table }));
var medusFromSql = queryableData.Provider.CreateQuery<MEDSU1s>(whereExpression).ToList();

Looping through members of a defined concrete type?

I'm doing comparisons across databases (with about 20 fields) and I've defined a concrete type to handle the comparison.
If my comparison fails, I want to loop through the individual items in the catch block and provide the user a list errors. Below, I've done it manually through the first few variables. Is there a more efficient way to loop this through all 20 fields? I started with a foreach (Object objectItem .. but not sure if that's the right way to go.
Any thoughts or much needed guidance?
try {
CollectionAssert.IsSubsetOf(orgs, members, "Error Matching testlist Fields");
}
catch
{
//OrgID
var sourceOrgID = orgs.Select(o => o.OrgID);
var destOrgID = members.Select(o => o.OrgID);
var errorList1 = sourceOrgID.Except(destOrgID);
string failedTests = null;
failedTests = string.Join("\n", errorList1);
Assert.IsTrue(0 == failedTests.Length, "The following Org IDs are not contained in the source: \n" + failedTests);
//DealerCode
var sourceDealerCode = orgs.Select(o => o.DealerCode);
var destDealerCode = members.Select(o => o.DealerCode);
var errorList2 = sourceDealerCode.Except(destDealerCode);
failedTests = null;
failedTests = string.Join("\n", errorList2);
Assert.IsTrue(0 == failedTests.Length, "The following Dealer Codes are not contained in the source: \n" + failedTests);
//orgkey
var sourceOrgKey = orgs.Select(o => o.OrgKey);
var destOrgKey = members.Select(o => o.OrgKey);
var errorList3 = sourceOrgKey.Except(destOrgKey);
failedTests = null;
failedTests = string.Join("\n", errorList3);
Assert.IsTrue(0 == failedTests.Length, "The following Org Keys are not contained in the source: \n" + failedTests);
You need reflection to do this:
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
GetColumn(orgList,property.Name);
}
var names = items.Select(x => x.GetType().GetProperty("prpname").GetValue(x));
public IEnumerable<object> GetColumn(List<Item> items, string columnName)
{
var values = items.Select(x =>
x.GetType().GetProperty(columnName).GetValue(x));//u can put your test code heere and you can loop it through object properties
}
you can create a list to store rslt and add results to the list else you can write output in logs files
If I haven't misunderstood your question, you could do something like this using shouldly
[TestMethod]
public void UnitTestExample()
{
var orgs = new Organisation
{
Ids = new List<int>{ 1,3 },
DealerCodes = new List<string> { "foo","bar"}
};
var members = new Organisation
{
Ids = new List<int> { 1,2,3 },
DealerCodes = new List<string> { "foo", "bar", "buzz" }
};
orgs.ShouldSatisfyAllConditions(
() => orgs.Ids.ShouldBe(members.Ids, ignoreOrder: true),
() => orgs.DealerCodes.ShouldBe(members.DealerCodes, ignoreOrder: true)
);
}
The output produced is:
You would still have to specify every property you want to check inside ShouldSatisfyAllConditions but shouldly does all the heavy lifting around comparing the lists and output the differences.
I'm assume that:
orgs is a IQueryable<T1>
members is a IQueryable<T2>
typeof(T1) == typeof(T2)
If so, that function could help:
private static void CompareRecords<TEntity>(IQueryable<TEntity> orgs, IQueryable<TEntity> members)
{
var entityType = typeof (TEntity);
var selectMethod = typeof (Queryable).GetMethods().First(x => x.Name == "Select");
var exceptMethod = typeof(Queryable).GetMethods().First(x => x.Name == "Except");
var toArrayMethod = typeof (Enumerable).GetMethods().First(x => x.Name == "ToArray");
foreach (var property in entityType.GetProperties())
{
var paramExpr = Expression.Parameter(entityType, "x");
var propExpr = Expression.Property(paramExpr, property.Name);
var delegateType = typeof (Func<,>).MakeGenericType(entityType, property.PropertyType);
var lambdaExpr = Expression.Lambda(delegateType, propExpr, paramExpr);
var parameterizedSelectMethod = selectMethod.MakeGenericMethod(entityType, property.PropertyType);
var parameterizedExceptMethod = exceptMethod.MakeGenericMethod(property.PropertyType);
var source = parameterizedSelectMethod.Invoke(null, new object[] {orgs, lambdaExpr});
var dest = parameterizedSelectMethod.Invoke(null, new object[] {members, lambdaExpr});
var errorList = parameterizedExceptMethod.Invoke(null, new[] {source, dest});
var errorListArray = toArrayMethod.MakeGenericMethod(property.PropertyType).Invoke(null, new[] {errorList});
var failedTests = string.Join("\n", ((IEnumerable)errorListArray).Cast<object>().Select(x => x.ToString()));
Assert.IsTrue(0 == failedTests.Length, $"The following {property.Name} are not contained in the source: \n{failedTests}");
}
}

How to create linq WHERE IN statement without using Contains()

I have a situation where I am using a linq provider that does not support the .Contains method to generate a WHERE IN clause in the query. I am looking for a way to generate the (Value = X OR Value = Y OR Value = Z) statement dynamically from the list of items to match. I haven't found a good example of building expression trees to do this.
Normally I would query this way:
var names = new string[] { "name1", "name2", "name3" }
var matches = query.Where(x => names.Contains(x.Name));
So far the closest thing I could find was to use the
Dynamic Linq Library and build a string to be interpreted but it feels a bit too hacky.
You don't even need an external library for something like this:
var names = new string[] { "name1", "name2", "name3" };
// Where MyClass is the type of your class
ParameterExpression par = Expression.Parameter(typeof(MyClass));
MemberExpression prop = Expression.Property(par, "Name");
Expression expression = null;
foreach (string name in names)
{
Expression expression2 = Expression.Equal(prop, Expression.Constant(name));
if (expression == null)
{
expression = expression2;
}
else
{
expression = Expression.OrElse(expression, expression2);
}
}
var query = ...; // Your query
if (expression != null)
{
// Where MyClass is the type of your class
var lambda = Expression.Lambda<Func<MyClass, bool>>(expression, par);
query = query.Where(lambda);
}
You can build a concatenation of Expression.OrElse, with the comparisons between the property Name and one of the strings.
In this particular case (3 strings), the resulting Expression, when looked from the debugger, is:
(((Param_0.Name == "name1") OrElse (Param_0.Name == "name2")) OrElse (Param_0.Name == "name3"))
Just improving the answer by xanatos:
var names = new string[] { "name1", "name2", "name3" };
var query = ...; // Your query
if (names.Any())
{
// Where MyClass is the type of your class
ParameterExpression par = Expression.Parameter(typeof(MyClass));
MemberExpression prop = Expression.Property(par, "Name");
var expression=names
.Select(v => Expression.Equal(prop, Expression.Constant(v)))
.Aggregate(Expression.OrElse);
// Where MyClass is the type of your class
var lambda = Expression.Lambda<Func<MyClass, bool>>(expression, par);
query = query.Where(lambda);
}

Expression.Lambda and query generation at runtime, simplest "Where" example

I was trying to generate a simple Lambda Expression at runtime with no luck... something like this:
var result = queryableData.Where(item => item.Name == "Soap")
Here is my example class and a fixture queryable:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
IQueryable<Item> queryableData = ...;
Then I generate a lambda expression at runtime correct code follows:
//"item" in "item =>..."
var item = Expression
.Parameter(typeof(Item), "item");
//property of my item, this is "item.Name"
var prop = Expression
.Property(item, "Name");
//then "Soap" in '... => item.Name=="Soap"'
var value = Expression.Constant("Soap");
//equality expression "==" in my primer
var equals = Expression.Equal(prop, value);
//then lambda
var lambda = Expression.Lambda<Func<Item, bool>>(equals, item);
//and here are the results
var results = queryableData.Where(lambda);
Big thanks to dtb for advice!
In the following query
var result = query.Where(item => item.Name == "Soap")
the lambda expression is
item => item.Name == "Soap"
You only need to construct this part, not the Where call which accepts an expression tree.
The expression tree for the lambda expression looks like this:
Lambda
/ \
Equal Parameter
/ \ item
Property \
"Name" Constant
| "Soap"
Parameter
item
In code:
var item = Expression.Parameter(typeof(Item), "item");
var prop = Expression.Property(item, "Name");
var soap = Expression.Constant("Soap");
var equal = Expression.Equal(prop, soap);
var lambda = Expression.Lambda<Func<Item, bool>>(equal, item);
var result = queryableData.Where(lambda);

Categories

Resources