LINQ - Joins in a dynamic query - linq

Because of some business decisions I need to change a bit of what I was doing. Yay me. :)
Currently, I have:
public IOrderedQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string,List<string>> filterDictionary)
{
string whereClause = "ProductGroupName='" + productGroupName + "' AND ProductTypeName='" + productTypeName + "'";
string comma = "";
foreach (KeyValuePair<string, List<string>> myKVP in filterDictionary)
{
comma = "";
if (myKVP.Value.Count > 0)
{
whereClause = String.Format("{0} AND FieldName = {1} AND FieldValue IN (", whereClause, myKVP.Key);
foreach (string value in myKVP.Value)
{
whereClause = String.Format("{0}{1}'{2}'", whereClause, comma, value);
comma = ",";
}
whereClause = String.Format("{0})", whereClause);
}
}
var q = db.ProductDetail
.Where (whereClause)
.OrderBy ("ProductTypeName");
return q;
}
Instead of foing this directly, I now need to join through 2 other tables to apply the filter correctly. I'm trying to figure out how to correctly join in a dynamic LINQ query. In TSQL it would be something like:
SELECT pd.*
FROM ProductDetail pd
INNER JOIN ProductFilterAssignment pfa ON pd.ProductID = pfs.ProductID
INNER JOIN ProductFilter pf ON pfs.FIlterID = pf.FIlterID
WHERE pf.FieldName = 'var1' AND pf.FieldValue IN ('var1a','var1b','var1c',etc)
AND pf.FieldName = 'var2' AND pf.FieldValue IN ('var2a','var2b','var2c',etc)

Ouch. Yeah, that's a complicated requirement. You know, lambdas are cumulative, so you can do this much simpler if you use successive linq expressions. Note that subsequent linq expressions are using the prior expression result and the entire isn't actually executed until iterated.
public IOrderedQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string,List<string>> filterDictionary)
{
// Initial select on productGroupName and productTypeName
var products = from product in db.ProductDetail
where product.ProductGroupName == productGroupName && product.ProductTypeName == productTypeName
select product;
// Now add each filter item present.
foreach (KeyValuePair<string, List<string>> myKVP in filterDictionary)
{
products = from product in products
join pfa in db.ProductFilterAssignment on product.ProductID equals pfa.ProductID
join pf in db.Product on pfa.FilterID equals pf.FilterId
where pf.FieldName == myKVP.Key && myKVP.Value.Contains(pf.FieldValue)
select product;
}
return products.OrderBy ("ProductTypeName");
}

I don't have a good "answer" for you, but more of an aside. Check out LINQPad. You might even see an ad on the right-hand side of this page, too. It is very slick for writing LINQ queries. It might help in writing and validating this and any other future LINQ query you write.

Try to use Spolty Framework. It helps to do dynamic query for Linq To SQL and Entity Framework. You can dynamically create left/inner join, add conditions, orderings and other things. If you use Spolty Framework then your code will be look like below:
public IQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string, List<string>> filterDictionary)
{
// create root node
JoinNode productDetailNode = new JoinNode(typeof(ProductDetail));
productDetailNode.AddConditions(new Condition("ProductGroupName", productGroupName),
new Condition("ProductTypeName", productTypeName));
// if there are conditions than we create joins
if (filterDictionary.Count > 0)
{
// create joinNode
// INNER JOIN ProductFilterAssignment pfa ON pd.ProductID = pfs.ProductID
JoinNode productFilterAssignmentNode = new JoinNode(typeof(ProductFilterAssignment));
productDetailNode.AddChildren(productFilterAssignmentNode);
// create joinNode
// INNER JOIN ProductFilter pf ON pfs.FIlterID = pf.FIlterID
JoinNode productFilterNode = new JoinNode(typeof(ProductFilter));
productFilterNode.AddChildren(productFilterNode);
foreach (KeyValuePair<string, List<string>> myKVP in filterDictionary)
{
// create condition pf.FieldName = {1} And AND pf.FieldValue IN ('var1a','var1b','var1c',etc)
productFilterNode.AddConditions(new Condition("FieldName", myKVP.Key),
OrCondition.Create("FieldValue", myKVP.Value.ToArray()));
}
}
// create result query by JoinNode productDetailNode
QueryDesigner queryDesigner = new QueryDesigner(db, productDetailNode).
OrderBy(new Ordering("ProductTypeName"));
return queryDesigner.Cast<ProductDetail>();
}

Related

Querydsl - filter on Left join with subquery

I have one of the complex query dynamically generated through Querydsl predicate and JPQL. I am also using the Q classes.
I am able to generate the following query by passing a predicate to the JPA repository.
select company0_.id as id1_18_,
company0_.name as name2_18_
from company company0_
left outer join companyAddress companyadd1_ on company0_.id=companyadd1_.company_id
where company0_.id in
(select companyadd2_.company_id
from companyAddress companyadd2_
where companyadd2_.address_type='1')
order by companyadd1_.addressline1;
but I want the query mentioned below
select company0_.id as id1_18_,
company0_.name as name2_18_
from company company0_
left outer join companyAddress companyadd1_ on company0_.id=companyadd1_.company_id
and companyadd1_.status = 'Active' -- New added(Failed to implement this)
where company0_.id in
(select companyadd2_.company_id
from companyAddress companyadd2_
where companyadd2_.address_type='1'
and and companyadd2_.status = 'Active') -- New Added(I am able to achieve this)
order by companyadd1_.addressline1;
We are using following kind of code, I can not possibly to share exact code due to security concern but you can help me by providing basic structure or code to achieve this.
final JPQLQuery<QCompanyAlias> subQuery = new JPAQuery<>();
BooleanExpression exp = null;
QueryBase<?> q = (QueryBase<?>) subQuery.from(qCompanyAddress);
if (requestMap.containsKey(CompanyQueryConstants.ADDRESS_TYPE)) {
BooleanExpression addrExp = null;
for (String addressType : addressTypes) {
if (addrExp == null) {
addrExp = qCompanyAddress.addressType.addressTypeCode.eq(addressType);
} else {
addrExp = addrExp.or(qCompanyAddress.addressType.addressTypeCode.eq(addressType));
}
}
exp = addrExp;
}
To add join on two conditions use
new JPAQuery(em)
.from(qCompany)
.leftJoin(qCompany, qCompanyAddress.company)
.on(
qCompany.id.eq(qCompanyAddress.company.id)
.and(qCompanyAddress.status.eq(status))
);
For subquery try to use this
final JPQLQuery<QCompanyAlias> subQuery = new JPAQuery<>();
BooleanExpression exp = null;
QueryBase<?> q = (QueryBase<?>) subQuery.from(qCompanyAddress);
if (requestMap.containsKey(CompanyQueryConstants.ADDRESS_TYPE)) {
BooleanExpression addrExp = null;
for (String addressType : addressTypes) {
if (addrExp == null) {
addrExp = qCompanyAddress.addressType.addressTypeCode.eq(addressType);
} else {
addrExp = addrExp.or(qCompanyAddress.addressType.addressTypeCode.eq(addressType));
}
}
exp = addrExp;
}
BooleanExpression statusExp = qCompanyAddress.status.eq(status);
if(exp == null) {
exp = statusExp;
} else {
exp = statusExp.and(exp);
}
But in your case I can't understand the reason for filtering by status twice. Subquery filtering should be enought. I suspect that you can achieve the same result without subquery. It depends on your entities.

How to map IQueryable from multiple tables in a LINQ query expression?

Is it possible to use AutoMapper to avoid the manual mapping bellow that gets executes a join on 2 tables in the select statement?
private IQueryable<HHDrawingNumber> GetDrawingNumbers()
{
var drawingNumbers = from dn in _unitOfWork.DrawingNumbers.Get()
join ld in _unitOfWork.CfgLocDrawing.Get() on dn.LocDrawingNum equals ld.DrawingsId
join ar in _unitOfWork.AssetReg.Get() on dn.AssetId equals ar.LastData
select new HHDrawingNumber
{
TagNumber = dn.TagNumber ?? string.Empty,
DrawingsId = ld.DrawingsId,
DrawingsCode = ld.DrawingsCode ?? string.Empty,
DrawingsDescription = ld.DrawingsDescription ?? string.Empty
};
return drawingNumbers;
}

Compare a linq value (int) to an int List using where clause

I have a linq query which joins a couple of tables and returns the value into an object. The query was working fine, till i added a where clause to in. Aftre the where clause, my query returns null.
Here's the code:
List<Int32> resourceSupervisorIdList = new List<Int32>();
resourceSupervisorIdList.Add(searchCriteriaTimesheet.ResourceId);
foreach (resource res in allSubordinateResources)
{
if (!resourceSupervisorIdList.Contains(res.id_resource))
resourceSupervisorIdList.Add(res.id_resource);
}
using (tapEntities te = new tapEntities())
{
var timesheetAll = (from tsh in te.timesheet_header
join rs in te.resources on tsh.id_resource equals rs.id_resource
join tsd in te.timesheet_detail on tsh.id_timesheet equals tsd.id_timesheet
where (resourceSupervisorIdList.Contains(rs.id_resource_supervisor))
select new TimesheetHeaderDetailsItem()
{
OrganizationId = rs.id_organization,
ProjectId = tsd.id_project,
StartDate = tsh.dte_period_start,
EndDate = tsh.dte_period_end,
ApprovedDate = tsh.dte_approved,
RejectedDate = tsh.dte_rejected,
SubmittedDate = tsh.dte_submitted,
});
if (timesheetAll == null || timesheetAll.Count() == 0)
{
return result;
}
}
Now, after adding the where clause, the code runs into the if condition. There are matching records in the table, but still i'm not able to get any records.
rs.id_resource_supervisor
is of type int in the mysql db.

Add dynamic property to Linq result set objects

I have object lists:
List<dynamic> prods = new List<dynamic>();
foreach (DataRow row in dataTable.Rows)
{
dynamic obj = new ExpandoObject();
IDictionary<string, object> oprops = obj;
foreach (System.Data.DataColumn col in dataTable.Columns)
oprops.Add(col.ColumnName, row[col.ColumnName]);
obj = oprops;
list.Add(obj);
}
And pricing list is filled the same way from the DataTable having product pricing.
I have this query:
var result = (from product in prods join price in pricing on product.ID equals price.ProductID select new { product, price }).ToList();
And I want to add some more properties to the result set.
foreach (dynamic obj in q)
{
obj.Test = "yes, testing."; // This line gives runtime error
Console.WriteLine(
"{0} - {1}",
obj.product.Name,
obj.price.UnitPrice);
}
I have some more objects/values to add to the each individual result object in foreach loop.
I am just unable to do that as it throws runtime error as follows:
<>f__AnonymousType1<dynamic,dynamic>' does not contain a definition for 'Test' and no extension method 'Test' accepting a first argument of type '<>f__AnonymousType1<dynamic,dynamic>' could be found (are you missing a using directive or an assembly reference?)
Add the property while querying the data so it is there to set in the foreach loop; eg:
var result = (from product in prods
join price in pricing on product.ID equals price.ProductID
select new { product, price, Test = (string)null, }
).ToList();
or, something I've never tried, maybe use an ExpandoObject:
var result = (from product in prods
join price in pricing on product.ID equals price.ProductID
select new ExpandoObject { product, price }
).ToList();
So, this is what I have found, we cant expand the linq query result set however creating a new exapndo objects solves the problem which enables adding new properties.
This is actually what I did:
List<dynamic> list = new List<dynamic>();
foreach (var o in q)
{
dynamic obj = new ExpandoObject();
IDictionary<string, object> oprops = obj;
oprops.Add("Test", "yes, testing!");
oprops.Add("product", o.product);
oprops.Add("price", o.price);
obj = oprops;
list.Add(obj);
}

LINQ anonym object with result to delimited string (LINQ to Entities does not recognize the method 'System.String ToString()' method)

I am trying to get a ; demlimited string of all the relatives that a person has.
Four tables are involved:
USERTAB
PERSON
PERSON_RELATION
RELATION_TAB
Query
from u in USERTAB
select new
{
Person = from p in PERSON where p.USERID == u.USERID
select new
{
PNo = p.NO,
Name = p.NAME
Relatives = (from r in PERSON_RELATION where r.PSEQ == p.PSEQ select new
{
Description = (from rel in RELATION_TYPE where rel.TYPE_SEQ == r.TYPE_SEQ select rel.DESCRIPTION).ToArray() //(or also tried .ToString())
})
}
}
I'd like the Description field to be a ";" delimited list of all the relatives a user (person) has.
Using a ToString on my Relatives object it only fails runtime with. LINQ to Entities does not recognize the method 'System.String ToString()' method
Example: Description = "Father, Brother, Cousin"
You need to be clear about what parts of your query get translated to SQL to run on the server, and what parts run in your local application. The key is to construct a simple query to retrieve all the data, then use .AsEnumerable() to ensure that the remaining transformations don't get translated to SQL, and finally transform the data into a form useful for you. Something like
var query =
from u in USERTAB
select new
{
Person =
from p in PERSON
where p.USERID == u.USERID
select new
{
PNo = p.NO,
Name = p.NAME
Relatives =
from r in PERSON_RELATION
where r.PSEQ == p.PSEQ
select new
{
Description =
from rel in RELATION_TYPE
where rel.TYPE_SEQ == r.TYPE_SEQ
select pos.DESCRIPTION
}
}
}
};
var enumerable =
from u in query.AsEnumerable()
select new
{
Person =
from p in u.Person
select new
{
PNo = p.PNo,
Name = p.Name
Relatives =
string.Join(", ",
from r in p.Relatives
from d in r.Description
select d.Description)
}
}
};
should do the trick.
This should work: write an extension method for strings of array, like so:
public static ToCsv(this string[] strings)
{
return String.Join("," strings);
}
Then just tack .ToCsv() at the end of your .ToArray() call above, and it should do the trick!

Resources