What is the appropriate LINQ query to this specific case? - c#

Given the following two classes:
public class Apple
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Worm
{
public int AppleId { get; set; }
public int WormType { get; set; }
public int HungerValue { get; set; }
}
All instances of Worm are given an AppleId equal to a randomly existing Apple.Id
public void DoLINQ(List<Apple> apples, List<Worm> worms, string targetAppleName, List<int> wormTypes )
{
// Write LINQ Query here
}
How can we write a Linq query which
finds all the elements in 'apples', whose 'Name' matches the 'targetAppleName'
AND
(does not "contain" the any worm with Wormtype given in Wormtypes
OR
only contains worms with Hungervalue equal to 500)?
Note that an instance of Apple does not actually 'contain' any elements of Worm, since the relation is the other way around. This is also what complicates things and why it is more difficult to figure out.
--Update 1--
My attempt which selects multiple apples with the same Id:
var query =
from a in apples
join w in worms
on a.Id equals w.AppleId
where (a.Name == targetAppleName) && (!wormTypes.Any(p => p == w.WormType) || w.HungerValue == 500)
select a;
--Update 2--
This is closer to a solution. Here we use two queries and then merge the results:
var query =
from a in apples
join w in worms
on a.Id equals w.AppleId
where (a.Name == targetAppleName) && !wormTypes.Any(p => p == w.WormType)
group a by a.Id into q
select q;
var query2 =
from a in apples
join w in worms
on a.Id equals w.AppleId
where (a.Name == targetAppleName) && wormTypes.Any(p => p == w.WormType) && w.HungerValue == 500
group a by a.Id into q
select q;
var merged = query.Concat(query2).Distinct();
--Update 3--
For the input we expect the LINQ query to use the parameters in the method, and those only.
For the output we want all apples which satisfy the condition described above.

You can use a let construct to find the worms of a given apple if you want to use query syntax:
var q =
from a in apples
let ws = from w in worms where w.AppleId == a.Id select w
where
(ws.All(w => w.HungerValue == 500)
|| ws.All(w => !wormTypes.Any(wt => wt == w.WormType)))
&& a.Name == targetAppleName
select a;
In method chain syntax this is equivalent to introducing an intermediary anonymous object using Select:
var q =
apples.Select(a => new {a, ws = worms.Where(w => w.AppleId == a.Id)})
.Where(t => (t.ws.All(w => w.HungerValue == 500)
|| t.ws.All(w => wormTypes.All(wt => wt != w.WormType)))
&& t.a.Name == targetAppleName).Select(t => t.a);
I wouldn't exactly call this more readable, though :-)

var result = apples.Where(apple =>
{
var wormsInApple = worms.Where(worm => worm.AppleId == apple.Id);
return apple.Name == targetAppleName
&& (wormsInApple.Any(worm => wormTypes.Contains(worm.WormType)) == false
|| wormsInApple.All(worm => worm.HungerValue == 500));
});
For each apple, create a collection of worms in that apple. Return only apples that match the required name AND (contain no worms that are in WormType OR only contain worms with a HungerValue of 500).

You were so close in your first attempt. But instead of a Join which multiplies the apples you really need GroupJoin which "Correlates the elements of two sequences based on key equality and groups the results". In query syntax it's represented by the join .. into clause.
var query =
from apple in apples
join worm in worms on apple.Id equals worm.AppleId into appleWorms
where apple.Name == targetAppleName
&& (!appleWorms.Any(worm => wormTypes.Contains(worm.WormType))
|| appleWorms.All(worm => worm.HungerValue == 500))
select apple;

Using lambda would look like this:
var result = apples.Where(a =>
a.Name == targetAppleName &&
(worms.Any(w => w.AppleId == a.Id && w.HungerValue >= 500)) ||
worms.All(w => w.AppleId != a.Id));
I think the lambda makes the code look a bit cleaner/easier to read, plus, the usage of.Any() and .All() is more efficient than a full on join IMHO... I haven't tested it with any heavy data so hard to speak with authority here (plus, there can't be that many apples...!)
BTW, this is the entire body of code. Kind of surprised it doesn't work for you. Maybe you missed something...?
public class Apple
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Worm
{
public int AppleId { get; set; }
public int WormType { get; set; }
public int HungerValue { get; set; }
}
void Main()
{
var apples = Enumerable.Range(1, 9).Select(e => new Apple { Id = e, Name = "Apple_" + e}).ToList();
var worms = Enumerable.Range(1, 9).SelectMany(a =>
Enumerable.Range(1, 5).Select((e, i) => new Worm { AppleId = a, WormType = e, HungerValue = i %2 == 0 ? a * e * 20 : 100 })).ToList();
DoLINQ(apples, worms, "Apple_4", new[] {4, 5});
}
public void DoLINQ(IList apples, IList worms, string targetAppleName, IList wormTypes)
{
// Write LINQ Query here
var result = apples.Where(a =>
a.Name == targetAppleName &&
(worms.All(w => w.AppleId != a.Id) || worms.Any(w => w.AppleId == a.Id && w.HungerValue >= 500)));
result.Dump(); // remark this out if you're not using LINQPad
apples.Dump(); // remark this out if you're not using LINQPad
worms.Dump(); // remark this out if you're not using LINQPad
}

I have modify your query but didn't tested yet lets have a look and try it. Hopefully it will solve your problem.
var query =
from a in apples
join w in worms
on a.Id equals w.AppleId into pt
from w in pt.DefaultIfEmpty()
where (a.Name == targetAppleName) && (!wormTypes.Any(p => p == w.WormType) || (w.HungerValue == 500))
select a;
Thanks.

Related

Use a filter to query a list with only child entity conditions EF Core 5

This works fine. But how do I customize the conditions for adding filters?input is the value entered by the user
var resultList = dbContext.BuyerBill
.Include(x=>x.BuyerBillItems.Where(x=>x.Status == input.Status && x.BuildTime > input.BeginTime && x.BuildTime < input.EndTime))
.ToList();
the way I want:
var query = WhereIf(input.Status!=null,x=>x.Status == input.Status);
query = WhereIf(input.BeginTime!=null,x=>x.BuildTime > input.BeginTime);
query = WhereIf(input.EndTime!=null,x=>x.BuildTime > input.EndTime);
this is my entity
public class BuyerBill
{
public BuyerBill()
{
BuyerBillItems = new List<BuyerBillItems>();
}
public int Id {get;set;}
public int BuyUserId {get;set;}
public int OrderId {get;set;}
public List<BuyerBillItems> BuyerBillItems { get; set; }
....
}
public class BuyerBillItems
{
public int Id {get;set;}
public int BuyerBillId {get;set;}
public decimal Fee {get;set;}
public int Status {get;set;}
public dateTime CreateTime {get;set;}
public BuyerBill BuyerBill {get;set;}
....
}
1、If the user does not select the time query
Select * from BuyerBill as buy inner join BuyerBillItems As item On buy.Id=item.BuyerBillId
where item.Status=1
2、If the user selects the time query
Select * from BuyerBill as buy inner join BuyerBillItems as item on buy.Id=item.BuyerBillId
where item.Status=1 and item.BuildTime > '2022-7-19' and item.BuildTime < '2022-7-19'
How to use efcore to implement the SQL conditions I described?
Mainly, I want to filter sub entities according to conditions. If there is only one entity, I know Where() method can be used for filtering, but I don't know how to use conditional filtering for sub entities
I solved the above problem with LinqKit, but now I have a new problem。
var predicatess = PredicateBuilder.New<BuyerBillItems>(true);
predicatess = predicatess.And(x => x.CreateTime > StringHelper.AddDateTime("2022-07-16"));
predicatess = predicatess.And(x => x.Status == 2);
//I'm dumb and this line of code seems redundant. But I don't know how to convert implicitly
var convertPredicate = (Expression<Func<BuyerBillItems, bool>>)predicatess;
var query = dbContext.BuyerBill.AsExpandable().Include(x => x.BuyerBillItems.Where(x => convertPredicate.Invoke(x)))
.Where(x => x.BuyerBillItems.Any(s => convertPredicate.Invoke(s)))
.Where(x => x.BuyUserId == 4);
//If you don't use Select, everything is normal
var result1 = query.ToList();
//BuyerBillItemsDto result is incorrect after using Select
var result2 = query.Select(x => new BuyerBillDto
{
Id = x.Id,
BuyUserId = x.BuyUserId,
OrderId = x.OrderId,
BuyerBillItemsDto = mapper.Map<List<BuyerBillItems>, List<BuyerBillItemsDto>>(x.BuyerBillItems)
}).ToList();
I have to use select to filter the columns to avoid performance loss
Curently you cannot use own extensions in Include body. So, consider to write query in the following way:
var resultList = dbContext.BuyerBill
.Include(x => x.BuyerBillItems.Where(x =>
(input.Status == null || x.Status == input.Status && x.BuildTime > input.BeginTime && x.BuildTime < input.EndTime)) &&
(input.BeginTime == null || x.BuildTime > input.BeginTime) &&
(input.EndTime == null || x.BuildTime > input.EndTime)
)
.ToList();
Update based on updated reuiqrements
For fully custom projection, there is not needed to build Include, because Select specifies which data is needed to retrieve from database.
Query can be rewritten using WhereIf extension:
var result = dbContext.BuyerBill
.Select(b => new BuyerBillDto
{
Id = b.Id,
BuyUserId = b.BuyUserId,
OrderId = b.OrderId,
BuyerBillItemsDto = b.BuyerBillItems
.AsQueryable()
.WhereIf(input.Status != null, x => x.Status == input.Status)
.WhereIf(input.BeginTime != null, x => x.BuildTime > input.BeginTime)
.WhereIf(input.EndTime != null, x => x.BuildTime > input.EndTime)
.Select(item => new BuyerBillItemsDto
{
Id = item.Id,
// other properties
})
.ToList()
}).ToList();

How to make use of join in linq having multiple tables and use orderby?

I need some help, i have a one working join, need the other one for third table? How can i create it? My orderby does not work either with year and need some help also. This is my logic as below and using Linq in sql;
// controller
public IList<ExtractionViewModel> GetExtractionViewModels()
{
ProductionManagementEntities db = new ProductionManagementEntities();
var scheduleList = (from p in db.ProductionDays
join w in db.Weeks on p.WeekId equals w.WeekId
// need other join here for the second table
orderby w.Year ascending // this is not working, year starts in 2017 instead of 2021 downwards
where(w.WeekNum == 9)
select new ExtractionViewModel
{
Year = w.Year,
Week = w.WeekNum,
Day = p.ProductionDate,
}).ToList();
return scheduleList;
}
// Model
public class ExtractionViewModel
{
public string Year { get; set; }
public int Week { get; set; }
public DateTime Day { get; set; }
public string VW250 { get; set; }
public string VW270 { get; set; }
public string VW250_2PA { get; set; }
public string VW_270_PA { get; set; }
}
//Controller
public IList<ExtractionViewModel> GetExtractionViewModels()
{
ProductionManagementEntities db = new ProductionManagementEntities();
var scheduleList = (from p in db.ProductionDays
from m in db.Models
join w in db.Weeks on p.WeekId equals w.WeekId
orderby w.Year descending
orderby m.Name ascending
where(m.Name== "VW250")
where(w.WeekNum == 9)
select new ExtractionViewModel
{
Year = w.Year,
Week = w.WeekNum,
Day = p.ProductionDate,
VW250 = m.Name
}).ToList();
return scheduleList;
}
Using a Linq query, this should work:
var scheduleList = (from p in db.ProductionDays
join w in db.Weeks on p.WeekId equals w.WeekId
join n in db.NewTable on p.WeekId equals n.WeekId
where w.WeekNum equals 9 and m.Name equals "VW250"
orderby w.Year ascending
select new ExtractionViewModel
{
Year = w.Year,
Week = w.WeekNum,
Day = p.ProductionDate,
Property = n.Property
}).ToList();
A simpler way, and also seems to run a bit quicker as well, is:
var scheduleList = db.ProductionDays
.Include(x => x.Weeks)
.Include(x => x.NewTable)
.Where(x => x.Week.WeekNum == 9)
.OrderBy(x => x.Week.Year)
.Select(x => new ExtractionViewModel {
x.Week.Year,
x.Week.WeekNum,
x.ProductionDate,
x.NewTable.Property
})
.ToList();
The second one is linq method and when debugging and stepping through I notice that they seem to be quicker than linq queries.
The problem with your query seemed to be the syntax on the where clause. You had where(w.WeekNum == 9) which may work but I have never seen that syntax. With linq, I have only worked with lambda expressions or where property equals value type syntax. I haven't tested this so if there is an error you will probably need to move the .OrderBy() statement to the bottom, but it should be fine.
In the question you don't mention what third table you would like to join but indicate that there is a third table. I added NewTable and NewTable.Property to indicate the third table and one of its columns/Properties.

LINQ to Entities does not recognize the method

This is my Code where I am fetching data.
var list = (from u in _dbContext.Users
where u.IsActive
&& u.IsVisible
&& u.IsPuller.HasValue
&& u.IsPuller.Value
select new PartsPullerUsers
{
AvatarCroppedAbsolutePath = u.AvatarCroppedAbsolutePath,
Bio = u.Bio,
CreateDateTime = u.CreationDate,
Id = u.Id,
ModifieDateTime = u.LastModificationDate,
ReviewCount = u.ReviewsReceived.Count(review => review.IsActive && review.IsVisible),
UserName = u.UserName,
Locations = (from ul in _dbContext.UserLocationRelationships
join l in _dbContext.Locations on ul.LocationId equals l.Id
where ul.IsActive && ul.UserId == u.Id
select new PartsPullerLocation
{
LocationId = ul.LocationId,
Name = ul.Location.Name
}),
Rating = u.GetPullerRating()
});
Now Here is my Extension.
public static int GetPullerRating(this User source)
{
var reviewCount = source.ReviewsReceived.Count(r => r.IsActive && r.IsVisible);
if (reviewCount == 0)
return 0;
var totalSum = source.ReviewsReceived.Where(r => r.IsActive && r.IsVisible).Sum(r => r.Rating);
var averageRating = totalSum / reviewCount;
return averageRating;
}
I have check this Post LINQ to Entities does not recognize the method
And I come to know I need to use
public System.Linq.Expressions.Expression<Func<Row52.Data.Entities.User, int>> GetPullerRatingtest
But how ?
Thanks
You can use conditionals inside LINQ to Entity queries:
AverageRating = u.ReviewsReceived.Count(r => r.IsActive && r.IsVisible) > 0 ?
u.ReviewsReceived.Where(r => r.IsActive && r.IsVisible).Sum(r => r.Rating) /
u.ReviewsReceived.Count(r => r.IsActive && r.IsVisible)
: 0
This will be calculated by the server, and returned as part of your list. Although with 10 million rows like you said, I would do some serious filtering before executing this.
Code within LINQ (to Entities) query is executed within database, so you can't put random C# code there. So you should either use user.GetPullerRating() after it is retrieved or create a property if you don't want to do the calculation every time.
You can also do:
foreach (var u in list)
u.Rating = u.GetPullerRating()
By the way, why is it extension method.

Adding a custom task in NopCommerce, return list of daily bestsellers

I'm working on creating a custom task in NopCommerce that lists most sold products of the day and show this data in the admin panel. E g lists all products that has been sold and order them by number of sold.
Iv'e found some code that runs a BestSeller report which is great, allthough this method is quite large and i'm uncertain which part i actually need in order to display the list of bestsellers. Also the method for bestsellers is a "virtual IList and the execute method is void.
This is the code for bestsellers:
class MostSoldProductsTask : ITask
{
private readonly IRepository<Order> _orderRepository;
private readonly IRepository<OrderProductVariant> _opvRepository;
private readonly IRepository<Product> _productRepository;
private readonly IRepository<ProductVariant> _productVariantRepository;
private readonly IDateTimeHelper _dateTimeHelper;
private readonly IProductService _productService;
public virtual IList<BestsellersReportLine> BestSellersReport(DateTime? startTime,
DateTime? endTime, OrderStatus? os, PaymentStatus? ps, ShippingStatus? ss,
int billingCountryId = 0,
int recordsToReturn = 5, int orderBy = 1, int groupBy = 1, bool showHidden = false)
{
int? orderStatusId = null;
if (os.HasValue)
orderStatusId = (int)os.Value;
int? paymentStatusId = null;
if (ps.HasValue)
paymentStatusId = (int)ps.Value;
int? shippingStatusId = null;
if (ss.HasValue)
shippingStatusId = (int)ss.Value;
var query1 = from opv in _opvRepository.Table
join o in _orderRepository.Table on opv.OrderId equals o.Id
join pv in _productVariantRepository.Table on opv.ProductVariantId equals pv.Id
join p in _productRepository.Table on pv.ProductId equals p.Id
where (!startTime.HasValue || startTime.Value <= o.CreatedOnUtc) &&
(!endTime.HasValue || endTime.Value >= o.CreatedOnUtc) &&
(!orderStatusId.HasValue || orderStatusId == o.OrderStatusId) &&
(!paymentStatusId.HasValue || paymentStatusId == o.PaymentStatusId) &&
(!shippingStatusId.HasValue || shippingStatusId == o.ShippingStatusId) &&
(!o.Deleted) &&
(!p.Deleted) &&
(!pv.Deleted) &&
(billingCountryId == 0 || o.BillingAddress.CountryId == billingCountryId) &&
(showHidden || p.Published) &&
(showHidden || pv.Published)
select opv;
var query2 = groupBy == 1 ?
//group by product variants
from opv in query1
group opv by opv.ProductVariantId into g
select new
{
EntityId = g.Key,
TotalAmount = g.Sum(x => x.PriceExclTax),
TotalQuantity = g.Sum(x => x.Quantity),
}
:
//group by products
from opv in query1
group opv by opv.ProductVariant.ProductId into g
select new
{
EntityId = g.Key,
TotalAmount = g.Sum(x => x.PriceExclTax),
TotalQuantity = g.Sum(x => x.Quantity),
}
;
switch (orderBy)
{
case 1:
{
query2 = query2.OrderByDescending(x => x.TotalQuantity);
}
break;
case 2:
{
query2 = query2.OrderByDescending(x => x.TotalAmount);
}
break;
default:
throw new ArgumentException("Wrong orderBy parameter", "orderBy");
}
if (recordsToReturn != 0 && recordsToReturn != int.MaxValue)
query2 = query2.Take(recordsToReturn);
var result = query2.ToList().Select(x =>
{
var reportLine = new BestsellersReportLine()
{
EntityId = x.EntityId,
TotalAmount = x.TotalAmount,
TotalQuantity = x.TotalQuantity
};
return reportLine;
}).ToList();
return result;
}
public void Execute()
{
throw new NotImplementedException("Return something");
}
I also have to return this list via the "Execute" -method that is implementing the ITask interface. My best guess is to have one method creating the list of bestsellers and have have the "Execute method implementing the first one and return it.
Thank you
I believe you misunderstood the purpose if ITask interface. ITask is used to run some background, non-UI tasks, so you can never get it to 'return' the list in admin panel.
What you want to do, instead, is to run it periodically and save the data in some custom table. Then use it

dynamic join based on where expression - linq/c#

I have a sp which builds a dynamic sql query based on my input params. I tried replicating in linq and somehow it seems incorrect.
My linq:
var result = from R in db.Committees.Where(committeeWhere)
join C in db.Employees.Where(employeeWhere) on R.PID equals C.PID
join K in db.CommitteeTypes.Where(committeesWhere) on R.PID equals K.PID
select new { R };
The 3 input params i have are:
1. Committee ID and/or
Employee ID and/or
Committee Type ID
Based on this, i want to be able to make the joins in my linq.
Note: i had to change table names and column names so please do not give thought on the names.
Sql snippet:
IF #committeeID is not null
set #wherestr = #wherestr + 'Committees.committeeID like' + #committeeID + #andstr
//...
IF len(#wherestr) > 6
SELECT #qrystr = #selectstr + #fromstr + left(#wherestr, len(#wherestr)-3) + ' ORDER BY Committees.committeeID DESC
EXEC (#qrystr)
Why do you need to use dynamic SQL? Wouldn't this work?
IQueryable<Committee> GetCommittees(int? committeeID, int? employeeID, int? committeeTypeID)
{
var result = from R in db.Committees.Where(c => committeeID == null || committeeID == c.ID)
join C in db.Employees.Where(e => employeedID == null || employeeID == e.ID)
on R.PID equals C.PID
join K in db.CommitteeTypes.Where(c => committeeTypeID == null || committeeTypeID == c.ID)
on R.PID equals K.PID
select R;
}
If that won't work, you can use different predicate expressions depending on your parameters:
Expression<Func<Committee, bool>> committeeWhere;
if(committeeID.HasValue)
{
int id = committeeID.Value;
committeeWhere = c => c.ID == id;
}
else
{
committeeWhere = c => true;
}
// etc
Update: Seeing your last comment, maybe you want something like this:
IQueryable<Committee> GetCommittees(int? committeeID, int? employeeID, int? committeeTypeID)
{
var result = db.Committees.Select(c => c);
if(committeeID.HasValue)
{
result = result.Where(c => c.ID = committeeID);
}
else if(employeeID.HasValue)
{
result = from R in result
join C in db.Employees.Where(e => employeeID == e.ID)
on R.PID equals C.PID
select R;
}
else if(committeeTypeID.HasValue)
{
result = from R in result
join K in db.CommitteeTypes.Where(ct => committeeTypeID == ct.ID)
on R.PID equals K.PID
select R;
}
return result;
}
If I may improve upon dahlbyk's answer... sometimes joining introduces duplicates. If you really intend to filter - then filter. Also - if you add the relationships in the LinqToSql designer, you'll have properties (such as Committee.Employees) which will be translated for you.
IQueryable<Committee> GetCommittees(int? committeeID, int? employeeID, int? committeeTypeID){
IQueryable<Committee> result = db.Committees.AsQueryable();
if(committeeID.HasValue)
{
result = result.Where(c => c.ID = committeeID);
}
if(employeeID.HasValue)
{
result = result
.Where(committee => committee.Employees
.Any(e => employeeID == e.ID)
);
}
if(committeeTypeID.HasValue)
{
result = result
.Where(committee => committee.CommitteeTypes
.Any(ct => committeeTypeID == ct.ID)
);
}
return result;
}

Categories

Resources