How would I write this as a single LINQ query? - c#

Using the following logic, what would the correct syntax be for a single LINQ query?
If Branch is Service, I want its parent, otherwise I want Branch.
Can you critique my attempt and let me know how I can improve it?
int branchId = 21;
var t = ctx.BranchInfos.Single(p => p.BranchID == branchId );
if (t.Type == BranchType.Service.ToString())
{
t = ctx.BranchInfos.Single(p => p.BranchID == t.Parent);
}

I suggest that if this is only needed in one place then what you have now is reasonably clear and should be kept.
If you are doing this a lot then do something like:
public static BranchInfo BranchOrServiceParent(
this IEnumerable<BranchInfo> input)
{
var t = BranchInfos.Single(p => p.BranchID == branchId);
if (t.Type == BranchType.Service.ToString())
t = input.BranchInfos.Single(p => p.BranchID == t.Parent);
return t;
}
Then using it is as simple as:
int branchId = 21;
var t = ctx.BranchInfos.BranchOrServiceParent();
If you subsequently need to parameterize/change thing things you can in a clear fashion.
If you subsequently find that the two possible trips to the database are a performance issue then you can either try a complex Linq query or accept that this probably needs to actually be done by a stored procedure.

var t = ctx.BranchInfos.Single(
p => (p.BranchID == branchId && p.Type != BranchType.Service.ToString) ||
(p.BranchID == GetBranchParentId(branchId) && p.Type == BranchType.Service.ToString));
where GetBranchParentId is a function wich returns the BranchId of the Branch who's id is passes as a parameter.
But, I like your original code, so I wouldn't use a single query to get my data.

I believe the following is equivalent to your code sample. I have added some mock code to turn this into a self-contained example.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Context ctx = new Context();
ctx.BranchInfos.Add(new BranchInfo() { Type = "NonService", BranchID = 20, Parent = 0 });
ctx.BranchInfos.Add(new BranchInfo() { Type = "Service", BranchID = 21, Parent = 20 });
ctx.BranchInfos.Add(new BranchInfo() { Type = "NonService", BranchID = 30, Parent = 20 });
int branchId = 21;
var t = (from a in ctx.BranchInfos
where a.BranchID == branchId
select a.Type != BranchType.Service.ToString() ? a :
(from b in ctx.BranchInfos
where b.BranchID == a.Parent
select b).Single()).Single();
Console.WriteLine(t.BranchID); // Prints 20
}
class Context
{
public List<BranchInfo> BranchInfos = new List<BranchInfo>();
}
class BranchInfo
{
public string Type;
public int BranchID;
public int Parent;
}
enum BranchType
{
Service = 0
}
}
}

This will probably get you what you need unless I messed up the logic.
Edit: Different approach
var t = ctx.BranchInfos.Where(p.BranchID == branchId).First(p => p.Type == BranchType.Service.ToString() ? p.Parent : p);

var t = ctx.BranchInfos.Where(p =>
(
p.BranchID == branchID &&
p.Type != BranchType.Service.ToSting()
)
||
(
p.Type == BranchType.Service.ToSting() &&
ctx.BranchInfos.Where(p => p.BranchID == branchID).FirstOrDefault() != null &&
p.BranchID == ctx.BranchInfos.Where(p => p.BranchID == branchID).FirstOrDefault().ParentID
)).FirstOrDefault();
The logic here is: (Get me Branch By ID IF the type is of service) OR (get me the parent of a Branch where I know the the child ID if the branch type is Service)
ALSO:
Even though there's a subquery in there, it will evaluate to a single hit to the DB because you're using the same Datacontext in the subquery.

I am quite sure you can do it with a single LINQ statement, but I am equaly sure that you should not do this. It will not improve the readabilty and hardly the performance.
var t = ctx.BranchInfos.Single(x =>
(
x.BranchID == branchID &&
x.Type != BranchType.Service.ToSting()
)
||
(
ctx.BranchInfos.Any(
y.BranchID == branchID) &&
y.Type == BranchType.Service.ToSting()) &&
x.BranchID == ctx.BranchInfos.Single(
y.BranchID == branchID) &&
y.Type == BranchType.Service.ToSting()).ParentID
)
);
Nice, isn't it? :D I still suggest not to use it. The first case is simple - if the item has the correct ID and and is not of type Service we have a match.
The second case is more tricky. We have to check if the item has the ID from the ParentID property of the item with the supplied ID but only if the item with the supplied ID is of type Service. Because we do not know if there is an item with the supplied ID and type Service when we check this, we must first check with Any() if there is such an item and rely on the conditional evaluation of the and.

Provided BranchInfo.Parent is the same type as BranchInfo:
int branchID;
var branchOrParent = db.BranchInfos
.Where(b => b.BranchID == branchID)
.Select(b => b.Type == BranchType.Service.ToString() ? b.Parent : b)
.FirstOrDefault();

This'll probably work but it's a bit awkward.
var t = ctx.BranchInfos.Where(p => p.BranchID == branchId)
.Select(p =>
p.Type != BranchType.Service.ToString()
? p
: ctx.BranchInfos.Single(t => p.Parent == t.BranchId)).FirstOrDefault();

I think you could do something like this:
var t = ctx.BranchInfos.FirstOrDefault(p => p.BranchID == branchId || p.BranchID == t.Parent);
Mike

Related

Check if parameter value is null or not inside query

In my query I am getting records based on RoleId and LocationId, some times the user may not pass location in that case I want to remove that filter and get information from all locations.
Currently I am doing this way
if(loc > 0)
{
var myResult = (from x in CSDB.Allocations
join s in CSDB.Managers
on x.ManagerId equals s.Id
Where x.RoleId == 2 && s.LocationId == loc
select new
{
x.name,
x.Date
}).ToList();
}
else
{
var myResult = (from x in CSDB.Allocations
join s in CSDB.Managers
on x.ManagerId equals s.Id
Where x.RoleId == 2
select new
{
x.name,
x.Date
}).ToList();
}
I am seeing if I can check if loc is null or not inside the query instead of using if else.
You can do something like this:
Where x.RoleId == 2 && (loc == null || s.LocationId == loc)
Also, you can do smth like this.
Where x.RoleId == 2 && (loc?.Equals(s.LocationId) ?? true)
If loc just int I would prefer to use a little bit changed #Salah Akbari answer:
Where x.RoleId == 2 && (loc == 0 || s.LocationId == loc)
Simply extract your managers and filter them if needed. That way you can as well easily apply more filters and code readability isn't hurt.
var managers = CSDB.Managers.AsQueryable();
if(loc > 0)
managers = managers.Where(man => man.LocationId == loc);
var myResult = from allocation in CSDB.Allocations
join manager in managers on allocation.ManagerId equals manager.Id
where allocation.RoleId == 2
select new
{
allocation.name,
allocation.Date
};

Difference between LINQ Lambda and SQL statement

I have the following lambda statement:
var resources = Db.Resource.Where(w => w.ResValue.Any(a => a.ApplicationFk == applicationPk) && w.CategoryFk == (categoryId ?? w.CategoryFk ) && w.IsEditable);
if (cultureIdsMissing!= null)
{
resources = resources.Where(w => w.ResValue.Any(a => cultureIdsMissing.Any(aa => aa == a.CultureFk) && a.Value == string.Empty));
}
This is not returning the result which I want, which is returned by:
SELECT Resource.ResourcePk, Resource.CategoryFk, Resource.Name, Resource.IsEditable, ResValue.ApplicatieFk, ResValue.CultureFk, ResValue.Value
FROM Resource
INNER JOIN ResValue ON Resource.ResourcePk = ResValue.ResourceFk
WHERE (ResValue.ApplicatieFk = 6)
AND (Resource.IsEditable = 1)
AND (ResValue.Value = '')
AND (ResValue.CultureFk = 1 OR ResValue.CultureFk = 2)
Not that cultureIdsMissing is a List containing both the numbers 1 and 2.
What am I missing or doing wrong with the lambda query?
I think you have to remove && w.CategoryFk == (categoryId ?? w.CategoryFk ) from your linq lemda expression. if categoryId = 1 then it will take only records with value 1. So try after remove that. Your linq code should be this.
var resources = Db.Resource.Where(w => w.ResValue.Any(a => a.ApplicationFk == applicationPk)&& w.IsEditable);
if (cultureIdsMissing!= null)
{
resources = resources.Where(w => w.ResValue.Any(a => cultureIdsMissing.Any(aa => aa == a.CultureFk) && a.Value == string.Empty));
}
You should take it from your sql statement :
Db.Resource
.Join(Db.ResValue
, rs => rs.ResourcePk
, resV => resv.resourceFk
, (rs, resv) => new { res = rs, resV = resV })
.Where(w => w.resv.ApplicatieFk == 6
&& w.res ==1
&& resv.Value == string.empty()
&& (resv.CultureFk == 1 || resv.CultureFk == 2))
It's not tested so maybe it won't work on first try.
I would translate the SQL to query comprehension syntax. In general, convert phrases in query comprehension order, use table aliases as range variables (or create range variables), and put unary/overall aggregate functions (such as TOP, DISTINCT or SUM) as function calls outside the whole query. For your SQL,
var ans = from r in Resource
where r.IsEditable == 1
join rv in ResValue on r.ResourcePk equals rv.ResourceFk
where rv.ApplicatieFk == 6 && rv.Value == "" && (rv.CultureFk == 1 || rv.CultureFk == 2)
select new { r.ResourcePk, r.CategoryFk, r.Name, r.IsEditable, rv.ApplicatieFk, rv.CultureFk, rv.Value };

How to combine the multiple part linq into one query?

Operator should be ‘AND’ and not a ‘OR’.
I am trying to refactor the following code and i understood the following way of writing linq query may not be the correct way. Can somone advice me how to combine the following into one query.
AllCompany.Where(itm => itm != null).Distinct().ToList();
if (AllCompany.Count > 0)
{
//COMPANY NAME
if (isfldCompanyName)
{
AllCompany = AllCompany.Where(company => company["Company Name"].StartsWith(fldCompanyName)).ToList();
}
//SECTOR
if (isfldSector)
{
AllCompany = AllCompany.Where(company => fldSector.Intersect(company["Sectors"].Split('|')).Any()).ToList();
}
//LOCATION
if (isfldLocation)
{
AllCompany = AllCompany.Where(company => fldLocation.Intersect(company["Location"].Split('|')).Any()).ToList();
}
//CREATED DATE
if (isfldcreatedDate)
{
AllCompany = AllCompany.Where(company => company.Statistics.Created >= createdDate).ToList();
}
//LAST UPDATED DATE
if (isfldUpdatedDate)
{
AllCompany = AllCompany.Where(company => company.Statistics.Updated >= updatedDate).ToList();
}
//Allow Placements
if (isfldEmployerLevel)
{
fldEmployerLevel = (fldEmployerLevel == "Yes") ? "1" : "";
AllCompany = AllCompany.Where(company => company["Allow Placements"].ToString() == fldEmployerLevel).ToList();
}
Firstly, unless AllCompany is of some magic custom type, the first line gives you nothing.
Also I have a doubt that Distinctworks the way You want it to. I don't know the type of AllCompany but I would guess it gives you only reference distinction.
Either way here'w what I think You want:
fldEmployerLevel = (fldEmployerLevel == "Yes") ? "1" : "";
var result = AllCompany.Where(itm => itm != null)
.Where(company => !isfldCompanyName || company["Company Name"].StartsWith(fldCompanyName))
.Where(company => !isfldSector|| fldSector.Intersect(company["Sectors"].Split('|')).Any())
.Where(company => !isfldLocation|| fldLocation.Intersect(company["Location"].Split('|')).Any())
.Where(company => !isfldcreatedDate|| company.Statistics.Created >= createdDate)
.Where(company => !isfldUpdatedDate|| company.Statistics.Updated >= updatedDate)
.Where(company => !isfldEmployerLevel|| company["Allow Placements"].ToString() == fldEmployerLevel)
.Distinct()
.ToList();
Edit:
I moved Distinct to the end of the query to optimize the processing.
How about trying like this;
AllCompany = AllCompany .Where(company => (company => company.Statistics.Created >= createdDate)) && (company.Statistics.Updated >= updatedDate));
If every part of query is optional (like created date, last update date..) then you can build linq query string.
Here's a sneaky trick. If you define the following extension method in its own static class:
public virtual IEnumerable<T> WhereAll(params Expression<Predicate<T> filters)
{
return filters.Aggregate(dbSet, (acc, element) => acc.Where(element));
}
then you can write
var result = AllCompany.WhereAll(itm => itm != null,
company => !isfldCompanyName || company["Company Name"].StartsWith(fldCompanyName),
company => !isfldSectorn || fldSector.Intersect(company["Sectors"].Split('|')).Any(),
company => !isfldLocation || fldLocation.Intersect(company["Location"].Split('|')).Any(),
company => !isfldcreatedDate || company.Statistics.Created >= createdDate,
company => !isfldUpdatedDate || company.Statistics.Updated >= updatedDate,
company => !isfldEmployerLevel || company["Allow Placements"].ToString() == fldEmployerLevel)
.Distinct()
.ToList();

How to write Linq depending if a value is provided or not

I am trying to write a LINQ statement with some optional where clauses. This is for a search. The user can select a specific site to search or search against all sites:
var query =
_db.STEWARDSHIP
.OrderBy(r => r.SITE.SITE_NAME)
.Where(r => r.SITE_ID == SiteId)
.Where(r => r.VISIT_TYPE_VAL.VISIT_TYPE_ID == VisitTypeId)
.Select(r => new
{
id = r.STEWARDSHIP_ID,
name = r.SITE.SITE_NAME,
visit_type = r.VISIT_TYPE_VAL.VISIT_TYPE_DESC,
visit_date = r.VISIT_DATE
});
return query;
So when the method gets SiteId = 14, for instance, no problem. However, when it gets SiteId = null, then that where clause should not be considered.
Thanks
Eric
That's easy:
var query = _db.STEWARDSHIP.OrderBy(r => r.SITE.SITE_NAME);
if (SiteId != null)
{
query = query.Where(r => r.SITE_ID == SiteId);
}
query = query.Where(r => r.SITE.SITE_TYPE_VAL.SITE_TYPE_ID == SiteTypeId)
.Select(r => new
{
id = r.STEWARDSHIP_ID,
name = r.SITE.SITE_NAME,
visit_type = r.VISIT_TYPE_VAL.VISIT_TYPE_DESC,
visit_date = r.VISIT_DATE
});
return query;
This works because queries compose nicely - and they really only represent queries; it's only when you try to fetch data from them that the query is actually executed.
Can't you just edit the where clause to something like
.Where(r=>SiteId == null || r.SiteId == SiteId)
you can use where clause in one statement ..like this ..
.Where(r => SiteID == null || r.SITE_ID == SiteID)
I'm stealing a trick from TSQL. Just check for the null value as well.
...
.Where(r => SiteID == null || r.SITE_ID == SiteID)
...
The SQL example is this:
WHERE (SITE_ID = #given OR #given IS NULL) --return matches or all
Though if that value is mutable and you want the value at the time the query was built, try this instead:
var localSiteID = SiteID;
...
.Where(r => localSiteID == null || r.SITE_ID == SiteID)
...

How to apply a filter in a LINQ to SQL expression only if results exist when the filter is applied?

I have a function I'd like to transform into a LINQ to SQL expression, but I can't figure out how. This function is called from within a LINQ query, once for each row in the result set. The productAreaId being passed in may or may not refer to valid data, so I have to check, and then only filter by productAreaId if any rows exist after applying the filter:
//Forgive the contrived example...
public static IQueryable<Order> GetOrders(int orderNumber, int? productAreaId,
OSDataContext db)
{
var orders = db.Orders.Where(o => o.OrderNumber == orderNumber &&
o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
!o.Deleted);
if (productAreaId != null)
{
var orders2 = orders.Where(o => o.ProductAreaId == productAreaId);
if (orders2.Any()) return orders2;
}
return orders;
}
I don't want to do it like this. I need a function that returns an expression without arbitrary code, so it will be composable. The above function is only displayed because it's the only way I know how to encapsulate this in a function.
I'd like to do something like this, with the contrived "ApplyFilterIfAnyResultExists" replaced with something that actually works:
public static Expression<Func<Order,bool>>
GetOrdersExpr(int orderNumber, int? productAreaId)
{
return o => o.OrderNumber == orderNumber &&
o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
!o.Deleted && (productAreaId == null ||
//Making up a fictional function. Is there a real way to do this?
o.ApplyFilterIfAnyResultExists(row =>
row.ProductAreaId == productAreaId)
);
}
Is there any way to apply that kind of filter within a LINQ to SQL expression? If not, any suggestions?
Thank you!Roy
EDIT:
This is the main query as I'd like it to look:
var customerData =
from c in db.Customers
select new
{
id = c.Id,
name = c.Name,
lastOrder =
db.Orders
.Where(GetOrdersExpr(c.LastOrderNumber,
c.PreferredProductAreaId))
.FirstOrDefault(),
allOrders = c.OrderForms
.Select(form =>
db.Orders
.Where(GetOrdersExpr(form.OrderNumber,
c.PreferredProductAreaId))
.FirstOrDefault()
)
.Where(o => o != null)
//How lastOrder used to be queried
//lastOrder =
// GetOrders(c.LastOrderNumber, c.PreferredProductAreaId, db)
// .FirstOrDefault()
};
It's also worth noting that Orders and Customers are in two different databases on the database server, but they're both referenced from the same DataContext here.
Maybe something like this:
var customerData =
from c in db.Customers
let orders = db.Orders.Where(o => o.OrderNumber == c.orderNumber &&
o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
!o.Deleted)
let orders2 = orders.Where(o => o.ProductAreaId == c.productAreaId)
select new
{
id = c.Id,
name = c.Name,
lastOrder = c.productAreaId != null && orders2.Any() ?
orders2.FirstOrDefault() :
orders.FirstOrDefault()
};
For your original method, this might work better:
public static IQueryable<Order> GetOrders(int orderNumber, int? productAreaId,
OSDataContext db)
{
var orders = db.Orders.Where(o => o.OrderNumber == orderNumber &&
o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
!o.Deleted);
if(productAreaId != null)
{
orders = orders.Where(
o => !orders.Any(o2 => o2.ProductAreaId == productAreaId) ||
o.ProductAreaId == productAreaId);
}
return orders;
}
This makes it so you're only doing a single database roundtrip. If the product area ID is provided, you will return orders where either:
none of the orders in the original query have that area ID, or
this order does have that area ID
It does make the query more complex, so I'd test it a bit to see if it really gives you any performance gains.
This won't translate very well to the function that you're suggesting, but if you share more information about how this code is getting called, I could probably give you some advice on how to avoid calling this function 20+ times.
Edit
Something like this should work:
var customerData =
from c in db.Customers
let productAreaId = c.PreferredProductAreaId
let orders =
db.Orders
.Where(o => o.OrderNumber == c.LastOrderNumber &&
o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
!o.Deleted)
.OrderBy(o => o.Date)
let lastOrderInProductArea = productAreaId != null
? orders.FirstOrDefault(o => o.ProductAreaId == productAreaId)
: null
select new
{
id = c.Id,
name = c.Name,
lastOrder = lastOrderInProductArea != null
? lastOrderInProductArea
: orders.FirstOrDefault()
};
public static IQueryable<Order> GetOrders(int orderNumber, int? productAreaId, OSDataContext db)
{
var orders = db.Orders.Where(o => o.OrderNumber == orderNumber &&
o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
!o.Deleted);
if (productAreaId != null)
{
var orders2 = orders.Where(o => o.ProductAreaId == productAreaId);
return orders2.Select(x => new {x, Type = 2 }).Concat(orders.Select(x => new {x, Type = 1 })).OrderBy(x => x.Type);
}
return orders;
}
This will return both results concatenated. First the results from orders2 then from orders2. This might help you.
If you really only want orders from one group you can do
GetOrders().Where(x => x.Type == GetOrders().Max(x => x.Type))
in order to restrict the query to the highest-priority orders. This will have suboptimal performance.

Categories