generalise where clause in linq query - c#

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(...);

Related

Entity Framework : common method for select data with dynamic where conditions for joined tables

I would like to create a method for common query. This query is joining three tables, and I would like to use property from all tables in the where clause.
The problem is when I create predicate on input then I can't implement it on selected anonymous object because all three tables are nested in anonymous object.
How should I implement this common method? I want to have it with different where conditions and use only one query.
The most common pattern for this is something like:
public async Task<IList<QueryResult>> GetQueryResults(
Expression<Func<Customer,bool>> customerFilter,
Expression<Func<Project, bool>> projectFilter )
{
var q = from c in Set<Customer>().Where(customerFilter)
join p in Set<Project>().Where(projectFilter)
on c.Id equals p.Id
select new QueryResult() { CustomerName = c.Name, ProjectName = p.Name };
return await q.ToListAsync();
}
Which you would call like:
var results = await db.GetQueryResults(c => c.Name == "a", p => true);
You can give the calling code more power by letting it change the base IQueryables used in the query, eg
public async Task<IList<QueryResult>> GetQueryResults(
Func<IQueryable<Customer>, IQueryable<Customer>> customerFilter,
Func<IQueryable<Project>, IQueryable<Project>> projectFilter )
{
var q = from c in customerFilter(Set<Customer>())
join p in projectFilter(Set<Project>())
on c.Id equals p.Id
select new QueryResult() { CustomerName = c.Name, ProjectName = p.Name };
return await q.ToListAsync();
}
which you would call like this:
var results = await db.GetQueryResults(c => c.Where(c => c.Name == "a"), p => p);

How to add sql 'when' functionality in c# linq

I have the following parameters:
public object GetDataByProjectCostID(string employeeid, DateTime costdate, int id = 0)
And the query:
var projectCost = (from pc in db.ProjectCosts
where pc.ProjectCostID == id
where System.Data.Entity.DbFunctions.TruncateTime(pc.CostDate) == costdate.Date
join p in db.Projects
on pc.ProjectID equals p.ProjectID
join sct in db.SubCostTypes
on pc.SubCostTypeID equals sct.SubCostTypeID
join ct in db.CostTypes
on sct.CostTypeID equals ct.CostTypeID
select new
{
p.ProjectName,
ct.CostTypeName,
ct.CostTypeID,
sct.SubCostTypeName,
pc.ProjectID,
pc.ProjectCostID,
pc.SubCostTypeID,
pc.Amount,
pc.Quantity,
pc.Note,
pc.CreatedBy,
sct.Unit,
pc.CreateDate,
pc.CostDate,
pc.ProjectCostImage
}).ToList();
Now my question is how i can add optional parameter in query.
Suppose if id not existed in request then i need to skip the where clause
where pc.ProjectCostID == id
In sql we can use when clause for that but what for in LINQ?
Thanks in advance.
try this
where (( id==0 || pc.ProjectCostID == id)
&& System.Data.Entity.DbFunctions.TruncateTime(pc.CostDate) == costdate.Date)
I use this technique in LinQ with the LinQ fluent form, you can try this:
//Make id nullable
public object GetDataByProjectCostID(string employeeid, DateTime costdate, int? id)
{
var projectCost = (from pc in db.ProjectCosts
//This expression is evaluated only if id has a value
where (!id.HasValue || pc.ProjectCostID == id)
&& System.Data.Entity.DbFunctions.TruncateTime(pc.CostDate) == costdate.Date
join p in db.Projects
on pc.ProjectID equals p.ProjectID
join sct in db.SubCostTypes
on pc.SubCostTypeID equals sct.SubCostTypeID
join ct in db.CostTypes
on sct.CostTypeID equals ct.CostTypeID
select new
{
p.ProjectName,
ct.CostTypeName,
ct.CostTypeID,
sct.SubCostTypeName,
pc.ProjectID,
pc.ProjectCostID,
pc.SubCostTypeID,
pc.Amount,
pc.Quantity,
pc.Note,
pc.CreatedBy,
sct.Unit,
pc.CreateDate,
pc.CostDate,
pc.ProjectCostImage
}).ToList();
}

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));

Include("myTable") in Linq to Entities in Silverlight

I need help, I have a method to Access to my Orders table:
public IQueryable<Orders> GetOrders()
{
return this.ObjectContext.Orders.Include("UnitDetail");
}
it Works very well, and I can see it from the window Data Source, and can see Orders and UnitDetail from here
But I need make some considerations for the selected rows, so I made the next method:
public IQueryable<Orders> GetOpenOrders(string _iduni)
{
ObjectSet<Orders> orders = this.ObjectContext.Orders;
ObjectSet<Estatus> estatus = this.ObjectContext.Estatus;
ObjectSet<DetailUnit> units = this.ObjectContext.DetailsUnit;
ObjectSet<ServiceType> servicetype = this.ObjectContext.ServiceType;
var query =
(from o in orders
join e in estatus on o.ID equals e.IDOrder
join u in units on o.IDUni equals u.IDUni
join t in servicetype on u.IDType equals t.IDType
where o.IDUni.Equals(_iduni)
&& !estatus.Any(oe => (oe.Estatus == "CANCELADA" || oe.Estatus == "CERRADA")
&& oe.IDOrder == o.ID)
select o).Distinct();
return query.AsQueryable();
}
This show me the correct recs, but what happend? why I don't see UnitDetail, when I inspect the result UnitDetail is null, how I can do for put the Include clausule in this new method??
Thanks a lot in advance
Because you haven't put the Include in your new method anywhere.
EDITED: to remove the unused joins.
You should be able to just use Include as you did in your GetOrders method, and so have it as a part of your existing orders variable. You are not actually using those joins anywhere, are you intending to?
Like this:
public IQueryable<Orders> GetOpenOrders(string _iduni)
{
var query =
(from o in this.ObjectContext.Orders.Include("UnitDetail")
where o.IDUni.Equals(_iduni)
&& !this.ObjectContext.Estatus.Any(oe => (oe.Estatus == "CANCELADA" || oe.Estatus == "CERRADA")
&& oe.IDOrder == o.ID)
select o).Distinct();
return query.AsQueryable();
}

build dynamic linq query in where clause

I want to build a dynamic query that able to extend the where clause condition if the value of the object is not empty string. Here is the code
public IEnumerable<Filter> GetFilter(Filter filter)
{
var y = ConditionalAttribute(filter);
var query =
from sub in Subscriptions
join u in Users
on sub.UserID equals u.Id
join od in Order_Details1
on sub.OD_Id equals od.OD_Id
join p in Products
on od.ProductId equals p.ProductId
where p.Type == "Testing" + y
select new Filter
{
//do something
};
for the Filter Object, here is the code
public class Filter
{
public int UserId { get; set; }
public string FirstName { get; set;}
}
the idea is if the filter.FirstName is not null it will append the where clause like this
public String ConditionalAttribute(Filter filter)
{
if(filter.FirstName != "")
return "&& u.First_Name = " + filter.FirstName + "";
}
Is there any way to append where clause by string like the code above? Because I've tried the approach above and it fails thanks
Create as many dynamic terms as you want to use in small methods that return an IQueryable.
public IQueryable ConditionalAttribute(IQueryable query, Filter filter)
{
if(filter.FirstName != "") {
query = query.Where(x => x.First_Name == filter.FirstName);
}
return query;
}
then apply them after the initial LINQ statement as you require:
public IEnumerable<Filter> GetFilter(Filter filter)
{
var query =
from sub in Subscriptions
join u in Users
on sub.UserID equals u.Id
join od in Order_Details1
on sub.OD_Id equals od.OD_Id
join p in Products
on od.ProductId equals p.ProductId
where p.Type == "Testing"
select new Filter
{
//do something
};
query = ConditionalAttribute(query, filter);
The statement isn't going to run until you project it into by using .ToList() or FirstOrDefault() or something like that so you can keep chaining onto query in this way as many times as you need.
I tend to prefer using standard C# syntax rather than the LINQ syntax, but, having said that, I find that LINQ syntax is more elegant when it comes to joining queries.
Here's an approach which utilizes the standard C# syntax to dynamically filter the source containers, then uses LINQ syntax to create the joining query.
public IEnumerable<Filter> GetFilter(Filter filter)
{
var y = ConditionalAttribute(filter);
IEnumerable<User> filteredUsers = Users;
if(!string.IsNullOrEmpty(filter.FirstName))
{
filteredUsers = filteredUsers.Where(u => u.First_Name == filter.FirstName);
}
var query =
from sub in Subscriptions
join u in filteredUsers
on sub.UserID equals u.Id
join od in Order_Details1
on sub.OD_Id equals od.OD_Id
join p in Products
on od.ProductId equals p.ProductId
where p.Type == "Testing" + y
select new Filter
{
//do something
};

Categories

Resources