Generate dynamic LINQ expression based on array - c#

I am using LINQ exprssion to query customers and filter them by state names. I have following query which works fine till the time I have 4 items in my statesArray.
public void GetCustomersForSelectedStates(string[] statesArray)
{
var customers = _repo.GetAllCustomers();
var filteredCustomers = from CUST in customers
join ST in States on CT.Tag_Id equals ST.Id
where CUST.ID == customer.ID && (ST.Name == statesArray[0] ||ST.Name ==statesArray[1] || ST.Name== statesArray[2]||ST.Name =statesArray[3])
//Do something with customers
}
I want following exprssion to be created dynamically:
(ST.Name == statesArray[0] ||ST.Name ==statesArray[1] ||
ST.Name== statesArray[2]||ST.Name =statesArray[3])
For example , create the dynamicQuery like below
var dynamicQuery = "(";
var dynamicQuery = "(";
for (int i = 0; i < statesArray.Count(); i++)
{
dynamicQuery += "ST.Name =="+statesArray[0];
if(i==statesArray.Count())
dynamicQuery+=")"
}
and then use it something like following,
//Psuedo code
var customers = _repo.GetAllCustomers();
var filteredCustomers = from CUST in customers
join ST in States on CT.Tag_Id equals ST.Id
where CUST.ID == customer.ID && Expression(dynamicQuery)

To do that via dynamic expressions basically means building a tree of:
(x.Foo == val0 || x.Foo == val1 || x.Foo == val2)
You can do that like this:
static Expression<Func<T, bool>> Where<T, TVal>(Expression<Func<T, TVal>> selector,
IEnumerable<TVal> values)
{
Expression result = null;
foreach (var val in values)
{
var match = Expression.Equal(
selector.Body,
Expression.Constant(val, typeof(TVal)));
result = result == null ? match : Expression.OrElse(result, match);
}
if (result == null) return x => true; // always match if no inputs
return Expression.Lambda<Func<T, bool>>(result, selector.Parameters);
}
with example usage:
string[] names = { "a", "c" };
var predicate = Where<Customer, string>(c => c.Name, names);
You can then use this predicate in the IQueryable<T>.Where extension method.
To combine it in your case, first do your regular LINQ:
var customers = _repo.GetAllCustomers();
var filteredCustomers = from CUST in customers
join ST in States on CT.Tag_Id equals ST.Id
where CUST.ID == customer.ID;
Now as a separate step apply the extra filter:
customers = customers.Where(predicate);
What this does is accept an input lambda of the form c => c.Name, then reuses the c.Name body for each c.Name == {val}, and reuses the c parameter as the parameter for the lambda we're creating. Each value from values becomes a typed constant. The Expression.Equal gives us the == test. The OrElse is the ||, noting that if result is null, this is the first item, so we just use the match expression itself.

You can add another join
var customers = _repo.GetAllCustomers();
var filteredCustomers = from CUST in customers
join ST in States on CT.Tag_Id equals ST.Id
join SA in statesArray on ST.Name equals SA // No need dynamic expression now
where CUST.ID == customer.ID
//Do something with customers

Related

Different OrderBy based on different filters

I have as scenario like this:
One supposed query
var list = (from p in context.table select p.field)
and different "orderby" based on the client choice
.
The simple way to do it would look like this:
if (filter.Equals("filter1"))
{
var list = (from p in context.table select p.field).OrderBy(w=> w.filter1);
}
else if (filter.Equals("filter2"))
{
var list = (from p in context.table select p.field).OrderBy(w=> w.filter2);
}
But since there is a lot of filters, it feels like it is an ugly practice to repeat the same query a lot of times just to change the OrderBy condition, does someone know what would be the best/cleaner approach?
If your filters vary in type, you can use a switch-case to select a filter:
var query = from p in context.table select p;
switch (filter) {
case "filter1":
query = query.OrderBy(r => r.filter1);
break;
case "filter2":
query = query.OrderBy(r => r.filter2);
break;
}
var list = query.Select(r => r.field).ToList();
If your filters are uniform in type, you can use a Dictionary to lookup the filter lambda Expression:
var q2 = from p in context.table select p;
var filterMap = new Dictionary<string, Expression<Func<Table, int>>> {
{ "filter1", (Table r) => r.filter1 },
{ "filter2", (Table r) => r.filter2 },
};
if (filterMap.TryGetValue(filter, out var orderFne))
q2 = q2.OrderBy(orderFne);
var l2 = query.Select(r => r.field).ToList();
first of all, I think, what you did is not bad at all. pretty straightforward. but if you really want to make it 'better' (actually not sure it is better, but at least it is a way):
var q = (from p in context.table select p)
.OrderBy(w => filter.Equals("filter1") ? w.filter1 : "")
.OrderBy(w => filter.Equals("filter2") ? w.filter2 : "")
.OrderBy(w => filter.Equals("filter3") ? w.filter3 : "")
...
;
var list = q.Select(x => x.field).ToList();
something like this
I would suggest another approach with extension method.
var q = (from p in context.table select p)
.OrderByIf(filter.Equals("filter1"), w => w.filter1)
.OrderByIf(filter.Equals("filter2"), w => w.filter2)
.OrderByIf(filter.Equals("filter2"), w => w.filter2)
...
;
var list = q.Select(x => x.field).ToList();
public static class QueryableExtensions
{
public static IQueryable<T> OrderByIf<T, TKey>(this IQueryable<T> query,
bool cond, Expression<Func<T, TKey>> prop)
{
if (cond)
{
query = query.OrderBy(prop);
}
return query;
}
}

Expression tree for groupby with where clause and than select

From UI dynamic column are coming as parameter in API and based on the parameter I have to fetch data from database.
Example : In the below code, based on the column if condition linq query is being executed. Now I want to make it generic so that it serve if new column condition come in future.
public List<string> GetFilteredTypeAhead(string searchText,string searchForRole,int fiscalyear,int fiscalPeriod)
{
if (searchForRole == "column1")
{
var accounts = (from a in _context.Account
where a.column1.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear ==fiscalyear
group a.column1 by a.column2 into g
select g.Key).ToList();
return accounts;
}
else if(searchForRole == "column2")
{
var accounts = (from a in _context.Account
where a.column2.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear
group a.column2 by a.column2 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column3")
{
var accounts = (from a in _context.Account
where a.column3.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear
group a.column3 by a.column3 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column4")
{
var accounts = (from a in _context.Account
where a.column4.StartsWith(searchText) && a.FiscalPeriod.Equals(fiscalPeriod) && a.FiscalYear.Equals(fiscalyear)
group a.column4 by a.column4 into g
select g.Key).ToList();
return accounts;
}
else
{
return new List<string>();
}
}
To convert it to generic. I created a expression tree.
static IQueryable<T> ConvertToExpression<T>(IQueryable<T> query, string propertyValue, PropertyInfo propertyInfo, int fiscalyear, int fiscalPeriod)
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(propertyValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(m, mi, c);
PropertyInfo propertyInfoFiscalPeriod = typeof(T).GetProperty("FiscalPeriod");
MemberExpression memberPropertyFiscalPeriod = Expression.Property(e, propertyInfoFiscalPeriod);
ConstantExpression right = Expression.Constant(fiscalPeriod);
Expression equalsFiscalPeriod = Expression.Equal(memberPropertyFiscalPeriod, Expression.Convert(right, typeof(Int16)));
PropertyInfo propertyInfoFiscalYear = typeof(T).GetProperty("FiscalYear");
MemberExpression memberPropertyFiscalYear = Expression.Property(e, propertyInfoFiscalYear);
right = Expression.Constant(fiscalyear);
Expression equalsFiscalYear = Expression.Equal(memberPropertyFiscalYear, Expression.Convert(right, typeof(Int16)));
Expression combineExpression = Expression.And(equalsFiscalPeriod, equalsFiscalYear);
Expression predicateBody = Expression.And(call, combineExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(predicateBody, e);
return query.Where(lambda);
}
And To call it I used code like below
"searchForRole" comes as parameter in as "column1","column2" etc
PropertyInfo propertyInfo = typeof(Account).GetProperty(searchForRole);
IQueryable<Account> query = _context.Account;
query = ConvertToExpression(query, searchText, propertyInfo,fiscalyear,fiscalPeriod);
var list = query.ToList();
Now this is working fine but the result having duplicate records. I wanted to have some distinct or group by on passed parameter column. In Simple words I wanted to remove if condition and make my search method generic. Please help.
It's possible, but IMHO it's better to keep the dynamic parts at minimum and use the C# compile time safety as much as possible.
The sample query in question
var accounts = (from a in _context.Account
where a.column1.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear ==fiscalyear
group a.column1 by a.column1 into g
select g.Key).ToList();
can be rewritten as follows
var accounts = _context.Account
.Where(a => a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear)
.Select(a => a.column1)
.Where(c => c.StartsWith(searchText))
.Distinct()
.ToList();
As you can see, the only dynamic part is a => a.column1 of type Expression<Func<Account, string>>. So all you need is a method like this:
static Expression<Func<T, M>> MemberSelector<T>(string name)
{
var parameter = Expression.Parameter(typeof(T), "e");
var body = Expression.PropertyOrField(name);
return Expression.Lambda<Func<T, M>>(body, parameter);
}
and to replace
.Select(a => a.column1)
with
.Select(MemberSelector<Account, string>(searchForRole))

how to convert sql statement to LINQ in c#

how to covert to LINQ :
from p in Product
join c in Catalog on c.Id equals p.CatalogId
join m in Manufacturer on m.Id equals p.ManufacturerId
where p.Active == 1
select new { Name = p.Name, CatalogId = p.CatalogId,
ManufacturerId = p.ManufacturerId, CatalogName = c.Name,
ManufacturerName = m.Name };
help!
Don't try to translate the query literally. In LINQ, you don't need to join entities as long as they already have a relationship because LINQ will automatically use the relationship to join the entities:
So assuming your Product has a Catalog property which in turn has Manufacturer property, you can write your LINQ without joins like this:
from p in Product
where p.Active == 1
select new {
Name = p.Name,
CatalogId = p.CatalogId,
ManufacturerId = p.ManufacturerId,
CatalogName = p.Catalog.Name,
ManufacturerName = p.Manufacturer.Name };
The solution is to build a temp result after the first join and use it as the first sequence in the second join
var result = product.Where(p => p.Active == 1), // 1st sequence
.join(catalog, // 2nd sequence
p => p.CatalogId, // selector from 1st sequence
c => c.Id, // selector from 2nd sequence
(p, c) => // take the elements where the selector match
new {ManufacturerId = p.ManufacturerId,
Name = p.Name,
CatalogId = p.CatalogId,
CatalogName = c.Name}) // result is 1st sequence next join
.Join(Manufacturer, // 2nd sequence 2nd join
r => r.ManufacturerId, // selector result 1st join
m => m.Id, // selector 2nd sequence 2nd join
(r, m) => // two elements where the selectors match
new {Name = r.Name, // build the result object
CatalogId = r.CatalogId,
ManufacturerId = r.ManufacturerId,
CatalogName = r.CatalogName,
ManumfacturerName = r.Name});
Piece of cake ^^
Just call ToString() method of IQueryable will return SQL Representation.
var query = from p in Product
join c in Catalog on c.Id equals p.CatalogId
join m in Manufacturer on m.Id equals p.ManufacturerId
where p.Active == 1;
string sqlQuery = query.ToString(); //SQL Query Saved Here

Linq Merge Queries

I have two queries that I would like to merge. This might be a left outer join, but it seems different.
The first query selects distinct stuff from a table:
var d = from d in db.Data
select (d.ID, d.Label, Value = 0).Distinct;
Lets suppose this returns the following:
{1,"Apple",0}
{2,"Banana",0}
{3,"Cabbage",0}
I then have another query that makes a different selection:
var s = from d in db.Data
where d.Label != "Apple"
select (d.ID, d.Label, d.Value);
This returns:
{2,"Banana",34}
{3,"Cabbage",17}
I then want a third query that joins the d and s together based upon their ID and their Label. I want the result to look like this:
{1,"Apple",0}
{2,"Banana",34}
{3,"Cabbage",17}
I'm basically just updating the numbers in the third query, but I have no idea how I should be doing this. It feels like it should be a simple join, but I just cannot get it to work.
This should work:
var query1 = from d in db.Data
select new { d.ID, d.Label, Value = 0 }.Distinct();
var query2 = from d in db.Data
where d.Label != "Apple"
select new { d.ID, d.Label, d.Value };
var result =
from d1 in query1
join d2 in query2 on new { d1.ID, d1.Label } equals new { d2.ID, d2.Label } into j
from d2 in j.DefaultIfEmpty()
select new
{
d1.ID,
d1.Label,
Value = d2 != null ? d2.Value : d1.Value
};
Note: are you sure you want to join on the ID and the label ? It seems rather strange to me... the label shouldn't be part of the key, so it should always be the same for a given ID
Here is one using method chain, which is my personal favorite.
var one = db.Data.Select(f => new {f.Id, f.Label, Value = 0});
var two = db.Data.Select(f => f).Where(f => f.Label != "Apple");
var three = one.Join(two, c => c.Id, p => p.Id, (c, p) => new {c.Id, c.Label, p.Value});
Could you just do
var s = from d in db.Data
select new
{
Id = d.ID,
Label = d.Label,
Value = (d.Label == "Apple" ? 0 : d.Value)
};

dynamic join based on where expression - linq/c#

I have a sp which builds a dynamic sql query based on my input params. I tried replicating in linq and somehow it seems incorrect.
My linq:
var result = from R in db.Committees.Where(committeeWhere)
join C in db.Employees.Where(employeeWhere) on R.PID equals C.PID
join K in db.CommitteeTypes.Where(committeesWhere) on R.PID equals K.PID
select new { R };
The 3 input params i have are:
1. Committee ID and/or
Employee ID and/or
Committee Type ID
Based on this, i want to be able to make the joins in my linq.
Note: i had to change table names and column names so please do not give thought on the names.
Sql snippet:
IF #committeeID is not null
set #wherestr = #wherestr + 'Committees.committeeID like' + #committeeID + #andstr
//...
IF len(#wherestr) > 6
SELECT #qrystr = #selectstr + #fromstr + left(#wherestr, len(#wherestr)-3) + ' ORDER BY Committees.committeeID DESC
EXEC (#qrystr)
Why do you need to use dynamic SQL? Wouldn't this work?
IQueryable<Committee> GetCommittees(int? committeeID, int? employeeID, int? committeeTypeID)
{
var result = from R in db.Committees.Where(c => committeeID == null || committeeID == c.ID)
join C in db.Employees.Where(e => employeedID == null || employeeID == e.ID)
on R.PID equals C.PID
join K in db.CommitteeTypes.Where(c => committeeTypeID == null || committeeTypeID == c.ID)
on R.PID equals K.PID
select R;
}
If that won't work, you can use different predicate expressions depending on your parameters:
Expression<Func<Committee, bool>> committeeWhere;
if(committeeID.HasValue)
{
int id = committeeID.Value;
committeeWhere = c => c.ID == id;
}
else
{
committeeWhere = c => true;
}
// etc
Update: Seeing your last comment, maybe you want something like this:
IQueryable<Committee> GetCommittees(int? committeeID, int? employeeID, int? committeeTypeID)
{
var result = db.Committees.Select(c => c);
if(committeeID.HasValue)
{
result = result.Where(c => c.ID = committeeID);
}
else if(employeeID.HasValue)
{
result = from R in result
join C in db.Employees.Where(e => employeeID == e.ID)
on R.PID equals C.PID
select R;
}
else if(committeeTypeID.HasValue)
{
result = from R in result
join K in db.CommitteeTypes.Where(ct => committeeTypeID == ct.ID)
on R.PID equals K.PID
select R;
}
return result;
}
If I may improve upon dahlbyk's answer... sometimes joining introduces duplicates. If you really intend to filter - then filter. Also - if you add the relationships in the LinqToSql designer, you'll have properties (such as Committee.Employees) which will be translated for you.
IQueryable<Committee> GetCommittees(int? committeeID, int? employeeID, int? committeeTypeID){
IQueryable<Committee> result = db.Committees.AsQueryable();
if(committeeID.HasValue)
{
result = result.Where(c => c.ID = committeeID);
}
if(employeeID.HasValue)
{
result = result
.Where(committee => committee.Employees
.Any(e => employeeID == e.ID)
);
}
if(committeeTypeID.HasValue)
{
result = result
.Where(committee => committee.CommitteeTypes
.Any(ct => committeeTypeID == ct.ID)
);
}
return result;
}

Categories

Resources