how to modify or insert where into expression tree - linq

by default I have this:
Expression<Func<ItemGroup, ItemGroupView>> Exp =
m => new ItemGroupView{
ID = m.id,
Name = m.name,
TotalCount = m.groupDetail.Sum(n => n.item.itemDetail.Count())
};
but in the runtime, I might want to add multiple filter. So for example, if I specify the status to 1 and category to mineral then it becomes
Expression<Func<ItemGroup, ItemGroupView>> Exp =
m => new ItemGroupView{
ID = m.id,
Name = m.name,
TotalCount = m.groupDetail.Sum(
n => n.item.itemDetail
.Where(o => o.status == 1 && o.category == "mineral")
.Count())
};
// ItemGroup.groupDetail is collection of ItemGroupDetail (n)
// ItemGroupDetail.item is Item
// Item.itemDetail is collection of ItemDetail (o)
// ItemDetail.item is Item
how do I modify the expression tree to insert multiple Where dynamically?
So far I do the default like this
private int _status;
private string _category;
internal Expression<Func<ItemDetail, bool>> whereStatus()
{
return o => o.status == _status;
}
internal Expression<Func<ItemDetail, bool>> whereCategory()
{
return o => o.category == _category ;
}
internal Expression<Func<ItemGroup, ItemGroupView>> GetEx()
{
return m => new ItemGroupView{
ID = m.id,
Name = m.name,
TotalCount = m.groupDetail.Sum(n => n.item.itemDetail.Count())
};
}
internal IQueryable<ItemGroupView> GetSelectQuery(IQueryabe<ItemGroup> ie)
{
ParameterExpression m = Expression.Parameter(typeof(ItemGroup), "m");
ParameterExpression n = Expression.Parameter(typeof(ItemGroupDetail), "n");
MemberInitExpression ex = (MemberInitExpression)GetEx().Body;
// ParameterReplacer is inherited from ExpressionVisitor
ex = (MemberInitExpression)new ParameterReplacer(
new ParameterExpression[] { m, n }).Visit(ex);
// ? ? ? ?
// how to modify the Expression if _status or _category is supplied?
Expression<Func<ItemGroup, ItemGroupView>> el =
Expression.Lambda<Func<ItemGroup, ItemGroupView>>
(ex, new ParameterExpression { m });
return ie.Select(el);
}
EDIT:
ItemGroup.itemDetail changed to ItemGroup.groupDetail, to avoid confusion between groups and items..

If you can create an expression that represents the Where() operation, you can then paste it into your main expression using LINQKit:
Expression<Func<IQueryable<ItemDetail>, IQueryable<ItemDetail>>> whereExpression=
id => id.Where(o => o.status == 1 && o.category == "mineral");
Expression<Func<ItemGroup, ItemGroupView>> Exp =
m => new ItemGroupView
{
ID = m.id,
Name = m.name,
TotalCount =
whereExpression.Invoke(m.itemDetail)
.Sum(n => n.item.itemDetail.Count())
};
Exp = Exp.Expand();
(Don't forget that last line, it's important.)

Related

order by lambda expression with expression trees in C#

My requirement is to order table according to a column in runtime. So I went through the path of parameter expression. I am able to achieve it when all the columns are present in single table. Here is my piece of code:
My requirement is to order table according to the buildStatusOrder during run time:
List<BuildStatusEnum> buildStatusOrder = new List<BuildStatusEnum>
{
BuildStatusEnum.h,
BuildStatusEnum.f,
BuildStatusEnum.a
};
ParameterExpression parameterExpression = Expression.Parameter(typeof(companyQueue));
MemberExpression memberExpression = Expression.PropertyOrField(parameterExpression, "buildStatusID");
MemberExpression valueExpression = Expression.Property(memberExpression, "Value");
Expression orderByExpression = Expression.Constant(buildStatusOrder.Count);
for (int statusIndex = buildStatusOrder.Count - 1; statusIndex >= 0; statusIndex--)
{
ConstantExpression constantExpression = Expression.Constant((int)buildStatusOrder[statusIndex]);
ConstantExpression indexConstantExpression = Expression.Constant(statusIndex);
BinaryExpression equalExpression = Expression.Equal(valueExpression, constantExpression);
orderByExpression = Expression.Condition(equalExpression, indexConstantExpression, orderByExpression);
}
MemberExpression hasValueExpression = Expression.Property(memberExpression, "HasValue");
ConditionalExpression nullCheckExpression = Expression.Condition(hasValueExpression, orderByExpression, Expression.Constant(buildStatusOrder.Count));
var orderByLambda = Expression.Lambda<Func<companyQueue, int>>(nullCheckExpression, parameterExpression);
using (KapowContext context = new KapowContext())
{
var queue = context.companyQueues
.Where(cq => cq.u.s.cq.userID == Utilities.Authentication.UserId)
.Where(cq => buildStatusOrder.Contains((BuildStatusEnum)cq.u.s.cq.buildStatusID))
.OrderBy(o => o.u.se.segmentId)
.ThenBy(orderByLambda)
This works fine. I have to display an additional column. It is from another table. So I joined tables. My lambda expression now looks like this:
var queue = context.companyQueues
.Join(context.abcd, cq => cq.robotID, r => r.robotId, (cq, r) => new { cq, r })
.Join(context.efgh, s => s.r.segmentId, se => se.segmentId, (s, se) => new { s, se })
.Join(context.hijk, u => u.s.cq.userID, us => us.userID, (u, us) => new { u, us })
.Where(cq => cq.u.s.cq.userID == Utilities.Authentication.UserId)
.Where(cq => buildStatusOrder.Contains((BuildStatusEnum)cq.u.s.cq.buildStatusID))
.OrderBy(orderByLambda)
Now my orderbyLambda is not working. I am getting the following error:
'System.Linq.Queryable.ThenBy<TSource,TKey>(System.Linq.IOrderedQueryable<TSource>, System.Linq.Expressions.Expression<System.Func<TSource,TKey>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Convert query to lambda

I want to know how can I write this query:
var query = from p in context.DimProduct
from psc in context.DimProductSubcategory
// on psc.ProductCategoryKey equals pc.ProductCategoryKey
where psc.EnglishProductSubcategoryName == subCategoryName
&& psc.ProductSubcategoryKey == p.ProductSubcategoryKey
select new DimProductDTO
{
ProductKey = p.ProductKey,
ProductSubcategoryKey = p.ProductSubcategoryKey,
EnglishProductName = p.EnglishProductName,
Size = p.Size,
StandardCost = p.StandardCost
};
I tried some queries, but no success. My problem is that I don't know how to have access to DimProduct and DimProductSubcategory.
Any suggestions?
context.DimProduct
.SelectMany(p => new { p, psc = context.DimProductSubcategory })
.Where(x => x.psc.EnglishProductSubcategoryName == subCategoryName
&& x.psc.ProductSubcategoryKey == x.p.ProductSubcategoryKey)
.Select(x => new DimProductDTO {
ProductKey = x.p.ProductKey,
ProductSubcategoryKey = x.p.ProductSubcategoryKey,
EnglishProductName = x.p.EnglishProductName,
Size = x.p.Size,
StandardCost = x.p.StandardCost })
However, you're not selecting anything from DimProductSubcategory, so I think the same can be done using Any() extension method:
context.DimProduct
.Where(x => context.DimProductSubcategory
.Any(y => y.EnglishProductSubcategoryName == subCategoryName
&& y.ProductSubcategoryKey == x.ProductSubcategoryKey))
.Select(x => new DimProductDTO {
ProductKey = x.ProductKey,
ProductSubcategoryKey = x.ProductSubcategoryKey,
EnglishProductName = x.EnglishProductName,
Size = x.Size,
StandardCost = x.StandardCost });
It should generate IN SQL statement within the query.
That is not exactly same query, but it produces same result via inner join (I believe that is more efficient than cross join)
context.DimProduct
.Join(context.DimProductSubcategory
.Where(x => x.EnglishProductSubcategoryName == subCategoryName),
p => ProductSubcategoryKey,
psc => ProductSubcategoryKey,
(p,psc) => new { p, psc })
.Select(x => new DimProductDTO {
ProductKey = x.p.ProductKey,
ProductSubcategoryKey = x.p.ProductSubcategoryKey,
EnglishProductName = x.p.EnglishProductName,
Size = x.p.Size,
StandardCost = x.p.StandardCost })
Also your original query can be rewritten as
var query = from p in context.DimProduct
join psc in context.DimProductSubcategory
on p.ProductSubcategoryKey equals psc.ProductSubcategoryKey
where psc.EnglishProductSubcategoryName == subCategoryName
select new DimProductDTO {
ProductKey = p.ProductKey,
ProductSubcategoryKey = p.ProductSubcategoryKey,
EnglishProductName = p.EnglishProductName,
Size = p.Size,
StandardCost = p.StandardCost
};
Generated SQL will look like:
SELECT [t0].[ProductKey], [t0].[ProductSubcategoryKey]
FROM [DimProduct] AS [t0]
INNER JOIN [DimProductSubcategory] AS [t1]
ON [t0].[EnglishProductSubcategoryName] = [t1].[ProductSubcategoryKey]
WHERE [t1].[EnglishProductSubcategoryName] = #p0

multiple conditions Linq extended

I need to consider multiple conditions to get value.
i mean if all conditions true that must give me a filtered answer.
Or one of them is true,rest are false...
so i need to write all possibilities??
if(a&b&c&d) else if(a&b&c) else if(a&c&d) else if(b&c&d) else if(a&b) else if (a&c)...etc ?? :))) Is there a shorter way to do this?
public List<ProductReqNoDate> GetRequestsQuery(string departmant, int reqStateID, string firstDate, string lastDate, string productName)
{
var db = new requestDBEntities();
bool dp = !string.IsNullOrEmpty(departmant);
bool pr = !string.IsNullOrEmpty(productName);
bool tm = !string.IsNullOrEmpty(firstDate) && !string.IsNullOrEmpty(lastDate);
bool rs = reqStateID > 0 ? true : false;
var query = (from r in db.requests
select new ProductReqNoDate
{
departmant = r.departmant,
reqNo = r.reqNo,
reqDate = r.reqDate,
productName = (from p in db.products where p.reqNo == r.reqNo select p.productName).FirstOrDefault()
}).ToList();
if (dp & pr & tm & rs)
{
var rState = (from ta in db.reqStates
where ta.reqStateID == reqStateID && ta.isActive == true
select ta.reqNo).ToList();
var prName = (from p in db.products where p.productName.Contains(productName) select p.reqNo).ToList();
DateTime dtfirstDate = Convert.ToDateTime(firstDate);
DateTime dtlastDate = Convert.ToDateTime(lastDate);
return query.Where(
r => rState.Contains(r.reqNo) //find by Request State
&& r.departmant == departmant //find by Departmant
&& (r.reqDate >= dtfirstDate && r.reqDate <= dtlastDate) //find by Date
&& prName.Contains(r.reqNo) //Find By Product Name
).ToList();
}
else if (dp & pr & tm) { /*return query.Where(...} */}
else if (pr & tm & rs) { /*return query.Where(...} */}
else if (dp & pr && rs) { /*return query.Where(...} */}
else if (dp & pr) { /*return query.Where(...} */}
//else if ...etc
}
Just add one Where condition at a time
public List<ProductReqNoDate> GetRequestsQuery(string departmant, int reqStateID, string firstDate, string lastDate, string productName)
{
var db = new requestDBEntities();
bool dp = !string.IsNullOrEmpty(departmant);
bool pr = !string.IsNullOrEmpty(productName);
bool tm = !string.IsNullOrEmpty(firstDate) && !string.IsNullOrEmpty(lastDate);
bool rs = reqStateID > 0 ? true : false;
var query = (from r in db.requests
select new ProductReqNoDate
{
departmant = r.departmant,
reqNo = r.reqNo,
reqDate = r.reqDate,
productName = (from p in db.products where p.reqNo == r.reqNo select p.productName).FirstOrDefault()
}).AsQueryable(); //AsQueryable is not always needed, but it shouldn't hurt and I don't feel like checking for this example.
if (dp)
{
query = query.Where(q => /*condition*/);
}
if (pr)
{
query = query.Where(q => /*condition*/);
}
if (tm)
{
query = query.Where(q => /*condition*/);
}
if (rs)
{
query = query.Where(q => /*condition*/);
}
return query.ToList();
}
You can build an expression with Expression.And like:
private Expression<Func<Request, bool>> GetPredicate(FilterDto filter)
{
Expression<Func<Request, bool>> predicate = r => r.ID == r.ID;
if (filter.Department.HasValue)
predicate = predicate.And(r => r.Department == filter.Department.Value);
if (filter.FirstDate.HasValue)
predicate = predicate.And(r => (r.reqDate >= filter.FirstDate.Value));
if (filter.LastDate.HasValue)
predicate = predicate.And(r => (r.reqDate <= filter.LastDate.Value));
/* ... */
return predicate;
}
Note: I put r => r.ID == r.ID here as first expression, just to have an expression to start with. This snippet does not fully cover your code, but I think it is enough as an example.
Use the expression in return query.Where(expression).ToList().

Optimize queries for Union, Except, Join with LINQ and C#

I have 2 objects (lists loaded from XML) report and database (showed bellow in code) and i should analyse them and mark items with 0, 1, 2, 3 according to some conditions
TransactionResultCode = 0; // SUCCESS (all fields are equivalents: [Id, AccountNumber, Date, Amount])
TransactionResultCode = 1; // Exists in report but Not in database
TransactionResultCode = 2; // Exists in database but Not in report
TransactionResultCode = 3; // Field [Id] are equals but other fields [AccountNumber, Date, Amount] are different.
I'll be happy if somebody could found time to suggest how to optimize some queries.
Bellow is the code:
THANK YOU!!!
//TransactionResultCode = 0 - SUCCESS
//JOIN on all fields
var result0 = from d in database
from r in report
where (d.TransactionId == r.MovementID) &&
(d.TransactionAccountNumber == long.Parse(r.AccountNumber)) &&
(d.TransactionDate == r.MovementDate) &&
(d.TransactionAmount == r.Amount)
orderby d.TransactionId
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount,
TransactionResultCode = 0
};
//*******************************************
//JOIN on [Id] field
var joinedList = from d in database
from r in report
where d.TransactionId == r.MovementID
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount
};
//Difference report - database
var onlyReportID = report.Select(r => r.MovementID).Except(joinedList.Select(d => d.TransactionId));
//TransactionResultCode = 1 - Not Found in database
var result1 = from o in onlyReportID
from r in report
where (o == r.MovementID)
orderby r.MovementID
select new TransactionList()
{
TransactionId = r.MovementID,
TransactionAccountNumber = long.Parse(r.AccountNumber),
TransactionDate = r.MovementDate,
TransactionAmount = r.Amount,
TransactionResultCode = 1
};
//*******************************************
//Difference database - report
var onlyDatabaseID = database.Select(d => d.TransactionId).Except(joinedList.Select(d => d.TransactionId));
//TransactionResultCode = 2 - Not Found in report
var result2 = from o in onlyDatabaseID
from d in database
where (o == d.TransactionId)
orderby d.TransactionId
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount,
TransactionResultCode = 2
};
//*******************************************
var qwe = joinedList.Select(j => j.TransactionId).Except(result0.Select(r => r.TransactionId));
//TransactionResultCode = 3 - Transaction Results are different (Amount, AccountNumber, Date, )
var result3 = from j in joinedList
from q in qwe
where j.TransactionId == q
select new TransactionList()
{
TransactionId = j.TransactionId,
TransactionAccountNumber = j.TransactionAccountNumber,
TransactionDate = j.TransactionDate,
TransactionAmount = j.TransactionAmount,
TransactionResultCode = 3
};
you may try something like below:
public void Test()
{
var report = new[] {new Item(1, "foo", "boo"), new Item(2, "foo2", "boo2"), new Item(3, "foo3", "boo3")};
var dataBase = new[] {new Item(1, "foo", "boo"), new Item(2, "foo22", "boo2"), new Item(4, "txt", "rt")};
Func<Item, bool> inBothLists = (i) => report.Contains(i) && dataBase.Contains(i);
Func<IEnumerable<Item>, Item, bool> containsWithID = (e, i) => e.Select(_ => _.ID).Contains(i.ID);
Func<Item, int> getCode = i =>
{
if (inBothLists(i))
{
return 0;
}
if(containsWithID(report, i) && containsWithID(dataBase, i))
{
return 3;
}
if (report.Contains(i))
{
return 2;
}
else return 1;
};
var result = (from item in dataBase.Union(report) select new {Code = getCode(item), Item = item}).Distinct();
}
public class Item
{
// You need also to override Equals() and GetHashCode().. I omitted them to save space
public Item(int id, string text1, string text2)
{
ID = id;
Text1 = text1;
Text2 = text2;
}
public int ID { get; set; }
public string Text1 { get; set; }
public string Text2 { get; set; }
}
Note that you need to either implement Equals() for you items, or implement an IEqualityComparer<> and feed it to Contains() methods.

Linq: Nested queries are better than joins, but what if you use 2 nested queries?

In her book Entity Framework Julie Lerman recommends using nested queries in preference to joins (scroll back a couple of pages).
In her example see populates 1 field this way, but what id you want to populate 2?
I have an example here where I would prefer to populate the Forename and Surname with the same nested query rather than 2 separate ones. I just need to know the correct syntax to do this.
public static List<RequestInfo> GetRequests(int _employeeId)
{
using (SHPContainerEntities db = new SHPContainerEntities())
{
return db.AnnualLeaveBookeds
.Where(x => x.NextApproverId == _employeeId ||
(x.ApproverId == _employeeId && x.ApprovalDate.HasValue == false))
.Select(y => new RequestInfo
{
AnnualLeaveDate = y.AnnualLeaveDate,
Forename = (
from e in db.Employees
where e.EmployeeId == y.EmployeeId
select e.Forename).FirstOrDefault(),
Surname = (
from e in db.Employees
where e.EmployeeId == y.EmployeeId
select e.Surname).FirstOrDefault(),
RequestDate = y.RequestDate,
CancelRequestDate = y.CancelRequestDate,
ApproveFlag = false,
RejectFlag = false,
Reason = string.Empty
})
.OrderBy(x => x.AnnualLeaveDate)
.ToList();
}
}
There's nothing wrong with your query, but you can write it in a way that is much simpler, without the nested queries:
public static List<RequestInfo> GetRequests(int employeeId)
{
using (SHPContainerEntities db = new SHPContainerEntities())
{
return (
from x in db.AnnualLeaveBookeds
where x.NextApproverId == employeeId ||
(x.ApproverId == employeeId && x.ApprovalDate == null)
orderby x.AnnualLeaveDate
select new RequestInfo
{
AnnualLeaveDate = x.AnnualLeaveDate,
Forename = x.Employee.Forename,
Surname = x.Employee.Surname,
RequestDate = x.RequestDate,
CancelRequestDate = x.CancelRequestDate,
ApproveFlag = false,
RejectFlag = false,
Reason = string.Empty
}).ToList();
}
}
See how I just removed your from e in db.Employees where ... select e.Forename) and simply replaced it with x.Employee.Forename. When your database contains the correct foreign key relationships, the EF designer will successfully generate a model that contain an Employee property on the AnnualLeaveBooked entity. Writing the query like this makes it much more readable.
I hope this helps.
try this
using (SHPContainerEntities db = new SHPContainerEntities())
{
return db.AnnualLeaveBookeds
.Where(x => x.NextApproverId == _employeeId ||
(x.ApproverId == _employeeId && x.ApprovalDate.HasValue == false))
.Select(y =>
{
var emp = db.Emplyees.Where(e => e.EmployeeId == y.EmployeeId);
return new RequestInfo
{
AnnualLeaveDate = y.AnnualLeaveDate,
Forename = emp.Forename,
Surname = emp.Surname,
RequestDate = y.RequestDate,
CancelRequestDate = y.CancelRequestDate,
ApproveFlag = false,
RejectFlag = false,
Reason = string.Empty
};
).OrderBy(x => x.AnnualLeaveDate).ToList();
}

Resources