How to convert a LINQ query to use lambda expressions - c#

I'm trying to switch this linq code to a lambda expression:
foreach (var id in orderLinesToSplit)
{
int result =
(from ol in orderLines
from splittedOl in orderLines
where ol.Key == id
&& ol.Value == splittedOl.Value
&& ol.Key != splittedOl.Key
select splittedOl.Key).FirstOrDefault();
}
The goal is to get a Dictionary<int, int> of pairs id (from oderLinesToSplit) and result (the result from the linq query). Can it be done with just one lambda expression?
Thanks

var result=
(
from ol in orderLines
from splittedOl in orderLines
where orderLinesToSplit.Contains(ol.Key)
&& ol.Value == splittedOl.Value
&& ol.Key != splittedOl.Key
select new
{
splittedOl.Key,
splittedOl.Value
}
).ToDictionary (v =>v.Key,v=>v.Value);
Or if you really want it in one expression. Then something like this:
var result=
(
db.orderLines.Join
(
db.splittedOl,
ol=>ol.Value,
splittedOl=>splittedOl.Value,
(ol,splittedOl)=>new
{
olKey=ol.Key,
splittedOlKey=splittedOl.Key
}
).Where(l =>l.olKey!= l.splittedOlKey && orderLinesToSplit.Contains(l.olKey))
.ToDictionary(l =>l.olKey,v=>v.splittedOlKey)
);
Where db is the linq data context

Related

How to dynamically add or remove where clause in LINQ

Here is my LINQ query that needs to update to dynamic where clause.
If parameter's id value is 0 then where clause should return all records else where clause should match records as per id value.
public async Task<IEnumerable<dynamic>> GetFilteredRowsByID(int id)
{
return from m in _context.TableName
where m.id == (id != 0 ? id : /*AllRecordsHere*/ )
join ...
join ...
select new {...}
}
I use Asp.Net Core 2.2. Is it possible without writing another method witout where clause for this?
I will use AsQueryable lazy query instead of one LINQ Query to do it. because it's more readable i think.
var query = (from m in _context.TableName.AsQueryable() select m );
if(id != 0)
query = query.Where(w=>w.id == id);
query = ( from m in query
join ...
join ...
select new {..}
)
Try condition:
where id == 0 || m.id == id
in case when id == 0, whole expression evaluates to true, otherwise it is false and second condition m.id == id would be checked.
I'm not sure that you can achieve this cleanly with 100% expression syntax, but you can with fluent syntax:
var result = _context.TableName;
if (id != 0) result = result.Where(m => m.Id == id);
result = result.Join(...).Join(...).Select(m => m.new {...});
A key benefit of fluent syntax is that it simplifies query composition.
You can freely mix expression and fluent syntax.
Move id != 0 ? outside of the equals expression:
public async Task<IEnumerable<dynamic>> GetFilteredRowsByID(int id)
{
return from m in _context.TableName
where id != 0 ? m.id == id : true
join ...
join ...
select new {...}
}
Hopefully EF would be able to optimize the where true to remove the condition entirely.

Filtering a IQueryable with results from another table

I have an EF query which gets products from the database.
var query = (from pPrice in db.ProductPricing
join prod in db.Products on pPrice.ProductID equals prod.ProductID
join productExt in db.ProductsExt on prod.ProductID equals productExt.ProductID into pExts
from prodExt in pExts.DefaultIfEmpty()
where (includeNonPublic || pPrice.ShowOnline == 1)
&& ((eventID.HasValue && pPrice.EventID == eventID) || (!eventID.HasValue && !pPrice.EventID.HasValue))
orderby prod.DisplayOrder
select new ProductPricingInfo()
{
Product = prod,
ProductPricing = pPrice,
ProductExtension = prodExt
});
I have a table where I can specify add-on products (products which can be bought once the parent item has been bought).
My query to fetch these add-on products is
var addOnProductsQuery = (from pa in db.ProductAddons
where pa.EventID == eventID && pa.StatusID == 1
select new { ProductID = pa.ChildProductId });
Now what I am trying to do is filter on the query variable to only return products which are not in the addOnProductsQuery result.
Currently I have
var addOnProducts = addOnProductsQuery.ToList();
query = query.Where(e => !addOnProducts.Contains(e.Product.ProductID));
But there is a syntax error on the Contains(e.Product.ProductID)) statement
Argument 1: cannot convert from int to anonymous type: int ProductID
Chetan is right in the comments, you need to select the integer from the object before using Contains.
You can do it in 2 ways:
First you could just take the integer initially:
var addOnProductsQuery = (from pa in db.ProductAddons
where pa.EventID == eventID && pa.StatusID == 1
select new pa.ChildProductId);
This should give int as type so you can use Contains later with no problem.
Second, if you want to keep the addOnProductsQuery unchanged:
var addOnProducts = addOnProductsQuery.Select(a => a.ProductID).ToList();
query = query.Where(e => !addOnProducts.Contains(e.Product.ProductID));

Using lambda expressions on joined basic LINQ query

When making a basic LINQ query you can later use lambda expressions to add a where clause like this: query.Where(c => (init.Contains(c.user)));.
My problem is that I need to add two where clauses on a query that uses a join in the basic LINQ query.
I'm trying to replace my old basic LINQ queries with added lambda expressions so that i prevent duplicated code.
This is my code;
var query = from c in db.Clgcom
join u in db.Dvusr
on c.Comaut equals u.Gitusr
// && (initialen.Contains(c.Tstusr) // <-- query.Where(c => (initialen.Contains(c.Tstusr)));
// This is what im trying to replace// ^^ This works because its in the same table
// || initialen.Contains(u.Clgusr)) // <-- What do i type when i want to include both these conditions?
&& (c.Modid.StartsWith("C")
|| c.Modid.StartsWith("M"))
select c;
if(filter != null){
query = query.Where(c => (initialen.Contains(c.Tstusr)
|| initialen.Contains(u.Clgusr)));
// This doesn't work
}
Is there a way to use a lambda expression that would achieve adding these two conditions in my where clause?
Or should i replace ALL basic LINQ queries with using lambda expressions?
Basically you need to also defer the select by selecting both c and u to begin with and later just selecting c.
var temp = from c in db.Clgcom
join u in db.Dvusr on c.Comaut equals u.Gitusr
where c.Modid.StartsWith("C") || c.Modid.StartsWith("M")
select new {c, u};
if(filter != null){
temp = temp.Where(x => initialen.Contains(x.c.Tstusr)
|| initialen.Contains(x.u.Clgusr));
var query = temp.Select(x => x.c);
If the relationship between Clgcom and Dvusr is many to one then you could do the following as Clgcom should have a Dvusr navigation property based on the foreign key relationship.
var query = from c in db.Clgcom
where (c.Modid.StartsWith("C") || c.Modid.StartsWith("M")) && c.Dvuser != null
select c;
if(filter != null){
query = query.Where(c => initialen.Contains(c.Tstusr)
|| initialen.Contains(c.Dvusr.Clgusr));

C# linq lambda expression for OR in a list

I have this scenario in which i query with FindByMany (which takes the lambda and returns post if user country and category matches, (as seen in the "else")
But now i need to customize the return with prefered subcategories from users, so what im doing is query n times foreach subcategory and just addRange. I dont want to query 5 times the db if the user has 5 subcategories as favorite, but i dont know how to apply a dinamic OR.
So my question is, how can this code be improved for performance.
var posts = new List<Content>();
if (request.UserId != 0)
{
var user = _userRepository.FindBy(u => u.Id == request.UserId);
if (user != null && user.SubCategories.Any())
{
foreach (var temp in user.SubCategories.Select(subCategory => _contentRepository.FindManyBy(
c =>
c.Country.Id == country.Id && c.Category.Id == theCategory.Id &&
c.SubCategory.Id == subCategory.Id).ToList()))
{
posts.AddRange(temp);
}
}
}
else
{
posts = _contentRepository.FindManyBy(
c => c.Country.Id == country.Id && c.Category.Id == theCategory.Id
).ToList();
}
Could you not just materalise the sub-categories into a list, and then in your FindBy use a thatlist.Contains()?
You can get the user's sub-categories with one query and then use the list and the Contains method to filter the relevant posts. Contains method is supported by most LINQ query provides and should be translated into a single database query.
var subcategories = user.SubCategories.ToList();
foreach (var temp in _contentRepository.FindManyBy(
c =>
c.Country.Id == country.Id && c.Category.Id == theCategory.Id &&
subcategories.Contains( subCategory.Id ) ).ToList()))
{
posts.AddRange(temp);
}
You can build expression for where clause using Expression.OrElse or use enter link description here
The core of the problem is that you're forcing query execution for each item instead of dynamically building the query. #Milney has the right idea; example code below.
IEnumerable<int> subCategoryIds = user.SubCategories.Select(x => x.Id);
var posts = _contentRepository.FindByMany(c => c.Country.Id == country.Id
&& c.Category.Id == theCategory.Id
&& subCategoryIds.Contains(c.SubCategoryId)).ToList();

generalise where clause in linq query

I have the following linq query:
var fileDocuments = (
from doc in fileUploads
from invoice in
(
from inv in _dbContext.SupplierInvoiceHeaders
where inv.InvoiceDocumentId == doc.ID || inv.JobSheetInvoiceId == doc.ID
select inv
).DefaultIfEmpty()
join pos in _dbContext.PurchaseOrders on invoice.PurchaseOrder.PurchaseOrderId equals pos.PurchaseOrderId into poss
from po in poss.DefaultIfEmpty()
join hdf in _dbContext.HelpDeskFaults on po.HelpdeskFaultId equals hdf.ID into hdfpo
from hs in hdfpo.DefaultIfEmpty()
join store1 in _dbContext.Stores on hs.StoreID equals store1.ID into hsf
from hdfStore in hsf.DefaultIfEmpty()
join js in _dbContext.JobSheets on invoice.SupplierInvoiceHeaderId equals js.SupplierInvoiceHeaderID into jss
from jobSheets in jss.DefaultIfEmpty()
join ch in _dbContext.ChildProjects on po.ChildProjectId equals ch.ID into chs
from childProjects in chs.DefaultIfEmpty()
join ph in _dbContext.ProjectHeaders on childProjects.ProjectHeaderID equals ph.ID into phs
from projectHeaders in phs.DefaultIfEmpty()
join ppmsl in _dbContext.PpmScheduleLines on projectHeaders.PPMScheduleRef equals ppmsl.ID into ppsmsls
from ppmScheduleLines in ppsmsls.DefaultIfEmpty()
join ss2 in _dbContext.Stores on ppmScheduleLines.StoreID equals ss2.ID into ssts
from store2 in ssts.DefaultIfEmpty()
where getJobWhereClause(invoice, hs, ppmScheduleLines, doc)
select new
{
doc.ID,
JobSheetId = jobSheets.DocumentID,
doc.Name,
doc.DateCreated,
doc.StoreID,
StoreName = doc.Store.Name,
DocumentType = doc.DocumentType.Name,
doc.DocumentTypeID
})
.AsEnumerable()
.Distinct()
.Select(d => new JobDocumentDto
{
ID = d.ID,
DocumentID = (d.JobSheetId) ?? d.ID,
DocumentName = d.Name,
DateCreated = d.DateCreated.ToString("dd/MM/yyyy"),
StoreName = d.StoreName,
DocumentTypeName = d.DocumentType,
DocumentTypeId = d.DocumentTypeID
}).OrderByDescending(x => x.ID);
return fileDocuments;
I have tried to separate the where clause into a func:
Func<SupplierInvoiceHeader, HelpDeskFault, PpmScheduleLineEntity, DocumentUploadEntity, bool> getJobWhereClause = (invoice, helpDeskFault, ppmScheduleLine, doc) =>
{
if (!string.IsNullOrEmpty(jobSearchParams.PIR) && string.IsNullOrEmpty(jobSearchParams.StoreName))
{
return invoice.PurchaseInvoiceReference == jobSearchParams.PIR;
}
if (string.IsNullOrEmpty(jobSearchParams.PIR) && !string.IsNullOrEmpty(jobSearchParams.StoreName))
{
return helpDeskFault.Store.Name.Contains(jobSearchParams.StoreName) || doc.Store.Name.Contains(jobSearchParams.StoreName) || ppmScheduleLine.Store.Name.Contains(jobSearchParams.StoreName);
}
return invoice.PurchaseInvoiceReference == jobSearchParams.PIR && (helpDeskFault.Store.Name.Contains(jobSearchParams.StoreName) || doc.Store.Name.Contains(jobSearchParams.StoreName) || ppmScheduleLine.Store.Name.Contains(jobSearchParams.StoreName));
};
I get the following error message:
Test method
IntegrationTests.Services.DocumentUploadServiceTests.Should_Search_By_PIR
threw exception: System.NotSupportedException: The LINQ expression
node type 'Invoke' is not supported in LINQ to Entities.
Which makes sense because there is no direct translation from the func to sql but is there a way I can create an expression that will achieve what I am after?
The easiest way is just to use an extension method and return an IQueryable, something like this (just fill in the ...):
public static IQueryable<fileUpload> FilterByThisStuff(this DbSet<fileUpload> db, ... invoice, ... helpDeskFault, ... ppmScheduleLine, ... doc)
{
if (!string.IsNullOrEmpty(jobSearchParams.PIR) && string.IsNullOrEmpty(jobSearchParams.StoreName))
{
return db.Where(invoice=>invoice.PurchaseInvoiceReference == jobSearchParams.PIR);
}
if (string.IsNullOrEmpty(jobSearchParams.PIR) && !string.IsNullOrEmpty(jobSearchParams.StoreName))
{
return db.Where(...);
}
return db.Where(...);
};
I've noticed you have an awful lot of joins there. Consider actually building your model with navigation properties instead. In anycase you can then use the above just like any other LINQ method, otherwise you will need to make some concrete class so you can use it in the extension method. The easy way:
var results=db.FileUploads
.FilterByThisStuff(a,b,c,d)
.Select(...)
.OrderBy(...)
.Take(...);

Categories

Resources