Basically I'm trying to write a query where it joins on select top 1 from a second table so something like:
SELECT Sum(pinfo.quantity + p.itemcount),
i.owner
FROM invoice i
JOIN purchase_info pinfo
ON pinfo.invoice = i.invid
JOIN (SELECT DISTINCT sku,
productlineid,
itemcount
FROM products WHERE productlineid in (13, 14)) p
ON p.sku = pinfo.item
WHERE i.owner = 22623
GROUP BY i.owner
Here's my pathetic attempt in linq that has somewhat invalid syntax, any ideas would be much appreciated.
(from i in _invoiceRepository.Table
join pi in _purchaseInfoRepository.Table on i.InvoiceId equals pi.InvoiceId
join p in (from p2 in _productRepository.Table where p2.Sku == pi.Item select new { p2.Sku, p2.ItemCount }).Take(1)
on pi.Item equals p.Sku
where i.MemberId == memberId &&
(p.ProductLineId == (int)ProductLines.InkCartridges ||
p.ProductLineId == (int)ProductLines.TonerCartridges)
select pi.Quantity * p.ItemCount)
.DefaultIfEmpty(0)
.Sum();
Here is my first stab at this.
From the sql, it looks like you want to find how many Ink and Toner Cartridges a particular customer has ordered from you ever.
This should give you the same results as the sql (this is depending on the order of the Products table since we are taking the top 1 without some sort of ordering being done:
var count = from i in _invoiceRepository.Table
where i.OwnerId == memberId
select new
{
OwnerId = i.OwnerId,
TotalProductCount = i.Purchases.Sum(pro => pro.Products
.Where(p => p.ProductLineId == (int)ProductLines.InkCartridges ||
p.ProductLineId == (int)ProductLines.TonerCartridges)
.Take(1)
.Sum(p => p.ItemCount * pro.Quantity))
};
Since I did not know the the classes of the three objects (Invoice, PurchaseInfo, and Product) I made a guess at what they are:
Invoice Class: I assume it has a list/collection of PurchaseInfos
public class Invoice
{
public int Id { get; set; }
public int OwnerId { get; set; }
public List<PurchaseInfo> Purchases { get; set; }
}
PurchaseInfos: An invoice has multiple PurchaseInfos, each one links to (ideally) one product but since the SKU is not unique I assome that this has a list/collection of Products in it.
public class PurchaseInfo
{
public int Id { get; set; }
public int Quantity { get; set; }
public int InvoiceId { get; set; }
public Invoice Invoice { get; set; }
public int Item {get;set;}
public List<Product> Products { get; set; }
}
Product Class: I assome that there is an Id field (not shown) or a composite primary key somewhere
public class Product
{
public int Sku { get; set; }
public int ProductLineId { get; set; }
public int ItemCount { get; set; }
public List<PurchaseInfo> PurchaseInfos { get; set; }
}
Hopefully you can take this a get what you need. If this is way off, please update question with the class definitions (you can remove unneeded properities if you want) so a better answer can be produced.
Related
Using EF core 5 and ASP.NET Core 3.1, I am trying to get a filtered collection based on a condition on its grandchildren collection.
I have the following entities:
public class Organisation
{
public int Id { get; set; }
public int? OrganisationId { get; set; }
public IEnumerable<Customer> Customers { get; set; }
}
public partial class Customer
{
[Key]
public uint Id { get; set; }
public int? EmployerId { get; set; }
public int? OrganisationId { get; set; }
public List<TimecardProperties> TimecardsProperties { get; set; }
}
public partial class TimecardProperties
{
[Key]
public int Id { get; set; }
public int? EmployerId { get; set; }
public int? Week { get; set; }
public short? Year { get; set; }
}
The goal is to get all Organisations that have at least one customer and the customer has at least 1 timecard property that is in week=34 and year=2021.
So far I have tried the following:
////necessary join to get Organisations for user id
IQueryable<Organisation> ouQuery = (from cou in _dbContext.Organisations
join uou in _dbContext.table2 on cou.OrganisationId equals uou.OrganisationId
where uou.UsersId == int.Parse(userId)
select cou)
.Where(cou => cou.Customers.Where(c => c.TimecardsProperties.Count > 0).Any())
.Include(cou => cou.Customers.Where(c => c.TimecardsProperties.Count > 0))
.ThenInclude(c => c.TimecardsProperties.Where(tc => tc.tWeek == 34 && tc.Year > 2020))
;
This returns a organisation list that each have a customers list but some customers have a count of timecards 0. I don't want to have organisation in the returned list that does not have at least one item in the timecards collection.
Also, it is too slow, and if I try to filter the produced list its even
slower (over 15 seconds)
I have also tried a raw sql query on the organisation db context but it is again very slow:
select distinct count(id) from organisation a where organisation_id in (
select organisation_id from customers where employer_id in (select distinct employer_id from timecards a
inner join timecard_components b on a.id=b.timecards_id
where week IN(
34) and year in (2021,2021) and invoice !=0 and type = 'time'
group by employer_id, week)
);
In general, I want to know the the total
count of the returned organisation collection for pagination (so I don't need to include all attributes of each entity)
as well as return only a part of the correct results, which satisfy the conditions,
an organisation list that has at least 1 timecards in
their customers by executing the query in the end like so:
ouQuery.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
I have also tried the EntityFramework.Plus and projection with no results.
How could I write this to achieve getting the total count of the organisation list and a part of these results (first 10) to display to the user?
Use navigation properties. This is the query you want:
var orgsQuery = dbContext.Organizations
.Where( o => o.Customers.Any( c =>
c.TimecardProperties.Any( tp =>
tp.Year = 2021
&& tp.Week = 34 ) ) );
Add includes and other predicates as needed
I have many to many relationship between entities user and group, I also have joining table GroupParticipants.
public class User
{
public string Id {get; set;}
public ICollection<GroupParticipant> Group { get; set;}
}
public class Group
{
public int Id { get; set; }
public ICollection<GroupParticipant> Participants { get; set; }
}
public class GroupParticipant
{
public int GroupId { get; set; }
public string ParticipantId { get; set; }
public User Participant { get; set; }
public Group Group { get; set; }
}
I need to select groups which user specified user did not join. I want to do something like:
string userId = 5;
var groupsAvailableToJoin = await _context.Groups
.Where(group => group.Participants.Id != userId);
Thanks!
A query like:
_context.Groups.Where(g =>
!_context.GroupParticipants.Any(gp => gp.UserId == userId && gp.GroupId == g.I'd
);
Should translate to:
SELECT * FROM Groups g
WHERE NOT EXISTS(SELECT null FROM groupParticipants gp WHERE gp.UserId = 5 AND gp.GroupId = g.Id)
Which should be a reasonably performant way of getting you what you're looking for.. I'm sure that the GroupParticipants columns are indexed..
There are various ways to write this - if you find a two step approach easier to understand, it's effectively the same as:
var joined = _context.GroupParticipants.Where(gp => gp.UserId == 5).Select(gp => gp.GroupId).ToList();
var notJoined = _context.Groups.Where(g => !joined.Contains(g.Id));
This one translates as a NOT IN (list,of,groups,they,are,in) for a similar effect
I am working on a project at work and am attempting to get all classes for a student from our database, however the same class keeps getting added to the list instead of all of the classes. I have practically the same query relating to another table which gets the classes of all professors, which for some reason works.
The entries in the database are not duplicates.
Here is the code I'm having issues with:
semester = "SP";
year = "19;
id = "0000001";
var tempCourses = (from b in db2.StuEnrolls where b.studentID == id && b.semester == semester && b.year == year orderby b.courseID select b).ToList();
I've also tried this:
var tempCourses = (from b in db2.StuEnrolls
where b.studentID == id && b.semester == semester && b.year == year
orderby b.courseID
select new StuEnrolls
{
studentID = b.studentID,
courseID = b.courseID,
year = b.year,
semester = b.semester,
dept = b.dept,
course = b.course,
section = b.section,
sectionTitle = b.sectionTitle,
courseGrade = b.courseGrade,
courseCredits = b.courseCredits
}).ToList();
Here is the similar code for professors that works:
var tempCourses = (from b in db2.CourseSectionsInstructor where b.instructorID == id orderby b.courseID select b).ToList();
As of now, I am getting 5 courses, all the same entry from the database. I should however be getting 5 unique entries. Here is an example of the output. The studentID has been edited out.
The DatabaseStructure looks like so:
public partial class StuEnrolls
{
public string studentID_courseID
{
get
{
return studentID + " " + courseID;
}
}
[Key]
public string studentID { get; set; }
public string courseID { get; set; }
public string year { get; set; }
public string semester { get; set; }
public string dept { get; set; }
public string course { get; set; }
public string section { get; set; }
public string sectionTitle { get; set; }
public string courseGrade { get; set; }
public decimal courseCredits { get; set; }
}
The table in the database is made up of:
Table Design
The problem seems to have had something to do with the table having two primary keys, and the Database Structure only having one key.
This code seems to have fixed the issue:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<StuEnrolls>().HasKey(k => new { k.studentID, k.courseID });
}
This are the Tables
public class Purchase
{
public long Id { get; set; }
public string WareName { get; set; }
public int Count { get; set; }
public DateTime BuyTime { get; set; }
public IList<Inventory> Inventory { get; set; }
}
public class Inventory
{
public long Id { get; set; }
public long PurchaseId { get; set; }
[ForeignKey(nameof(PurchaseId))]
public Purchase Purchase { get; set; }
public int SaledCount { get; set; }
}
I try to do a Query like this:
SELECT SUM(x.[icout]) AS icount FROM
(
SELECT p.[Count] - ISNULL(
(SELECT SUM(i.SaledCount) FROM Inventory AS i WHERE i.PurchaseId = p.Id )
,0) AS [icout]
FROM Purchase AS p
WHERE p.WareName ='WareName5' AND
(
p.[Count] - ISNULL((SELECT SUM(i.SaledCount) FROM Inventory AS i WHERE i.PurchaseId = p.Id ),0) > 0
)
) AS x`
var left = _Db.Set<Purchase>().Include(p=>p.Inventory)
.Where(p=>p.WareName == WareName)
.Select(p => p.Count - p.Inventory.Sum(i => i.SaledCount)).Sum();
But it doesn't work when I target a real database (sqlite / sqlserver).
And it works fine when I use inMemoryDatabase.
Could anyone help me?
Ok this is just an attempt with the use of let, just tried it on my DNet Core site, and it ran nicely (I don't have the same entities as you do of course).
var left = from purchase in _Db.Set<Purchase>().Include(p=>p.Inventory)
where purchase != null && purchase.WareName == WareName
let sum = purchase.Inventory.Sum(i => i.SaledCount ?? 0)
select (purchase.Count - sum).Sum();'
Here is a straight forward solution that should not be giving you a N+1 problem.
It solves the "complex" solution in-memory. sometimes the cost has to be somewhere, and in memory sum is better than N+1
var left = (from purchase in _Db.Set<Purchase>().Include(p=>p.Inventory)
where purchase != null && purchase.WareName == WareName
select new ()
{
count = purchase.Count - sum,
invCounts = purchase.Inventory.where(a=> a.SaledCount!= null).Select(a=> a.SaledCount);
}).ToList().Select(a => a.count - invCounts.Sum());
Note
Use ICollection instead of IList as it's features translates better to SQL
Also in the constructor initialize the collections to a new HashSet<T>() so you don't end up with a null exception. Think of it like this, you cannot count the element of null but you can count the elements of a collection with 0 rows.
public class Purchase
{
public Purchase(){
Inventory = new HashSet<Inventory>();
}
public ICollection<Inventory> Inventory { get; set; }
}
I have a scenario where I have 3 tables:
News [Id, Name]
Users [Id, Name]
Likes [Id, News, User]
I am trying to write a query that will return all news as well as a column that returns true or false (if the a specific user has or not liked a content).
On SQL I would to something like this:
select *,
(select top 1 id from newslike nl where nl.newsid = n.id and nl.userid = 1)
from News n
How can I achieve that with an EF query?
I already add to my class a NotMapped property for this boolean value
Edit:
public partial class News
{
public int Id { get; set; }
public virtual ICollection<NewsLike> NewsLike { get; set; }
[NotMapped]
public bool LikedByCurrentUser { get; set; }
}
public partial class NewsLike
{
public int Id { get; set; }
public int NewsId { get; set; }
public int UserId { get; set; }
public virtual News News { get; set; }
public virtual User User { get; set; }
}
News.Where(n => n.Likes.Any(l => l.UserId == userId));
Here userId is the ID of a desired user. You'll get all news that the user liked.
Assuming there's a navigation property from News to `Likes:
db.News.Select(
n => new {n.Id, n.Name, UserLikes = n.Likes.Any(l => l.userid == 1))}
);
Since I have a not mapped property to store the bool value if the user has liked the content, can my query returns a list of news instead of the anonymous type?
Sure, just create a new News item instead of an anonymous type:
db.News.Select(
n => new News {
Id = n.Id,
Name = n.Name,
LikedByCurrentUser = n.NewsLike.Any(l => l.userid == currentUser))}
);