build dynamic linq query in where clause - c#

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

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

In EF Core 5, how to apply Groupby after applying FirstOrDefault in parent Query

I am working on .NET 5 CORE app with Entity Framework Core 5.0.6 Version.
Ultimately, I need to group my record. In EF CORE 3, I was able to use group by on script before applying ToList() but I believe is not possible in EF Core 5. So to achieve group by, I am intended to pull record and then apply group by on it.. Unless there is a better way?
var contraventionImages = (from contravention in db.Contraventions
join contraventionGuideImage in db.ContraventionGuideImages on contravention.ContraventionId equals contraventionGuideImage.ContraventionId into con_guide_img
from contraventionGuideImages in con_guide_img.DefaultIfEmpty()
join image in db.GuideImages on contraventionGuideImages.GuideImageId equals image.GuideImageId into guide_img
from guideImages in guide_img.DefaultIfEmpty()
where contravention.ContraventionId == ContraventionId
select new ContraventionGuideImageView
{
Contravention = contravention,
ContraventionGuideImages = contraventionGuideImages,
GuideImages = guideImages
})
.FirstOrDefault()
;
So I have pulled the record, ending script with FirstOrDefault(), now in second script where I am trying to inspect and group Item; is Unable to do so. I am not able to apply select on query result I have in previous step
public class ContraventionGuideImageView
{
public Contravention Contravention { get; set; }
public ContraventionGuideImage ContraventionGuideImages { get; set; }
public GuideImage GuideImages { get; set; }
}
OK, the edit has made it much clearer to see. It looks like your confusion is that after selecting the first record you're trying to do further queries on that record which:
Won't work
Seems pointless.
By doing the call to FirstOrDefault, you're getting an object (in your case ContraventionGuideImageView); you need to do any grouping/ordering on the queryable before you do the first or default, for example:
var contraventionImages = (from contravention in db.Contraventions
join contraventionGuideImage in db.ContraventionGuideImages on contravention.ContraventionId equals contraventionGuideImage.ContraventionId into con_guide_img
from contraventionGuideImages in con_guide_img.DefaultIfEmpty()
join image in db.GuideImages on contraventionGuideImages.GuideImageId equals image.GuideImageId into guide_img
from guideImages in guide_img.DefaultIfEmpty()
where contravention.ContraventionId == ContraventionId
select new ContraventionGuideImageView
{
Contravention = contravention,
ContraventionGuideImages = contraventionGuideImages,
GuideImages = guideImages
})
.ToList() //shouldn't make a difference, but is for the benefit of Toxic seeing the process.
.GroupBy(x => x.contravention)
.FirstOrDefault();
HOWEVER, the query is so complex that I'd argue it'd be better as a stored procedure.
found the answer;
Option A:
var t7 = (from contravention in db.Contraventions
.Include(x => x.ContraventionGuideImages)
.ThenInclude(x => x.GuideImage)
where contravention.ContraventionId == ContraventionId
select new
{
contravention = contravention,
GuideImages = contravention.ContraventionGuideImages.Select(x => x.GuideImage)
}).FirstOrDefault();
Option B:
var contraventionImages = (from contravention in db.Contraventions
join contraventionGuideImage in db.ContraventionGuideImages on contravention.ContraventionId equals contraventionGuideImage.ContraventionId into con_guide_img
from contraventionGuideImages in con_guide_img.DefaultIfEmpty()
join image in db.GuideImages on contraventionGuideImages.GuideImageId equals image.GuideImageId into guide_img
from guideImages in guide_img.DefaultIfEmpty()
where contravention.ContraventionId == ContraventionId
select new { contravention, contraventionGuideImages, guideImages }).ToList();
var guideImagesList = (from contravention in contraventionImages.Select(x => x.contravention)
group contraventionImages.Select(x => x.guideImages ) by contravention into groupedItem
select new
{
contravention = groupedItem.Key,
contraventionGuideImages = groupedItem.ToList()
}).FirstOrDefault();

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

How to use Join and Where to return an IList?

How can I achieve a join and a where in C# MVC using something like Linq or EF Join?
This is the equivalent SQL I am trying to achieve.
select * from promotion P
JOIN PromotionsClaimed PC
on PC.PromotionId = P.objectid
where PC.userId = #USERID
This method should return a list of the promotions for the user. First, I get a list of all of the promotions, then I get the composite list of claimed promotions for the user. This is what I have so far.
public IList<Promotion> GetRewardsForUser(string userId)
{
//a list of all available promotions
IList<Promotion> promos = _promotionLogic.Retrieve();
//contains a list of Promotion.objectIds for that user
IList<PromotionsClaimed> promosClaimed = _promotionsClaimedLogic.RetrieveByCriteria(t => t.userId == userId);
//should return a list of the Promotion name and code for the rewards claimed by user, but a complete list of Promotion entities would be fine
var selectedPromos =
from promo in promos
join promoClaimed in promosClaimed on promo.objectId equals promoClaimed.PromotionId
select new { PromoName = promo.Name, PromoCode = promo.Code };
return selectedPromos;
}
I realize there are a lot of problems here. I'm trying to learn Linq and Entity Framework, but I don't know how to add the where clause to an IList or if there is an easier way to accomplish this.
It seems to me like there would be a way to filter the promotion list where it contains the Promotion.objectId in the promosClaimed list, but I don't know the syntax.
public IList<Promotion> GetRewardsForUser(string userId)
{
//a list of all available promotions
IList<Promotion> promos = _promotionLogic.Retrieve();
//contains a list of Promotion.objectIds for that user
IList<PromotionsClaimed> promosClaimed = _promotionsClaimedLogic.RetrieveByCriteria(t => t.userId == userId);
//should return a list of the Promotion name and code for the rewards claimed by user, but a complete list of Promotion entities would be fine
var selectedPromos =
(from promo in promos
join promoClaimed in promosClaimed on promo.objectId equals promoClaimed.PromotionId
select new { PromoName = promo.Name, PromoCode = promo.Code }).ToList();
return selectedPromos;
}
If I understood your question correctly, you could do something like this:
public IList<Promotion> GetRewardsForUser(string userId)
{
//contains a list of Promotion.objectIds for that user
IList<PromotionsClaimed> promosClaimed = _promotionsClaimedLogic
.RetrieveByCriteria(t => t.userId == userId);
var promotionIds = promosClaimed.Select(p => p.PromotionId).ToList();
IList<Promotion> promos = _promotionLogic.Retrieve()
.Where(p => promotionIds.Contains(p.objectId))
.Select(p => new { PromoName = p.Name, PromoCode = p.Code });
return selectedPromos;
}
The claimed promotions should be already filtered by a user so this should possibly work.
First of all, you're using entity framework? Or are you just trying to do join of two collections?
Because if you are using EF, you're thinking the wrong way. In the entity the proper way is to use include, for example:
public DbSet<promotion > promotion { get; set; }
public DbSet<PromotionsClaimed> PromotionsClaimed{ get; set; }
Context.promotion.Include(o => o.PromotionsClaimed).FirstOrDefault(s => s.Id == USERID);
If you need only join two collection using linq, you can do that.
var userId = 1;
var test =
(
from p in promos
join pc in promosClaimed on p.objectid equals pc.PromotionId
where pc.userId == userId
select p
).ToList();
Have you tried just to add your condition to your code? Like:
var selectedPromos =
from promo in promos
join promoClaimed in promosClaimed on promo.objectId equals promoClaimed.PromotionId
where promosClaimed.UserId == userId
select new { PromoName = promo.Name, PromoCode = promo.Code };
That should work or I just didn't understand you

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

Categories

Resources