Query in asp.net core - mysql

I have a query table like this, people let me ask how can I get the Positions data of the current user when I get the userid to query with the Document table.
var claimsIdentity = _httpContextAccessor.HttpContext.User.Identity as ClaimsIdentity;
var userId = claimsIdentity.FindFirst(ClaimTypes.NameIdentifier)?.Value.ToString();
var query = from c in _context.Documents
join u in _context.Users on c.UserID equals u.Id
join p in _context.Positions on u.Id equals p.UserID
where c.UserID.ToString() == userId
select new { c, u, p };

The data you query is almost enough, but it contains duplicate entries of Document and Position. If you want the final query to be put in a single object like this:
{
User = ...,
Documents = ...,
Positions = ...
}
You just need to project it using Linq-to-object (because all the data is loaded and ready for projection on the client):
var result = (from document in _context.Documents
join user in _context.Users on document.UserID equals user.Id
join position in _context.Positions on user.Id equals position.UserID
where document.UserID.ToString() == userId
select new { document, user, position }).AsEnumerable()
.GroupBy(e => e.user.Id)
.Select(g => new {
User = g.First().user,
Documents = g.GroupBy(e => e.document.Id)
.Select(e => e.First().document),
Positions = g.GroupBy(e => e.position.Id)
.Select(e => e.First().position)
}).FirstOrDefault();
If you don't want to fetch the user info, you don't need to join that DbSet but instead join the two Document and Position directly like this:
var result = (from document in _context.Documents
join position in _context.Positions on document.UserID equals position.UserID
where document.UserID.ToString() == userId
select new { document, position }).AsEnumerable()
.GroupBy(e => e.document.UserID)
.Select(g => new {
Documents = g.GroupBy(e => e.document.Id)
.Select(e => e.First().document),
Positions = g.GroupBy(e => e.position.Id)
.Select(e => e.First().position)
}).FirstOrDefault();
Note that I suppose your Document and Position both have its own primary key property of Id (adjust that to your actual design).
Finally, usually if your User entity type exposes navigation collection properties to the Document and Position. We can have a better (but equal) query like this:
var user = _context.Users
.Include(e => e.Documents)
.Include(e => e.Positions)
.FirstOrDefault(e => e.Id.ToString() == userId);
It's much simpler because all the joining internally translated by the EFCore. The magic is embedded right into the design of navigation collection properties.
I would like to talk about the important note of the condition UserID.ToString() == userId or Id.ToString() == userId. You should avoid that because it would be translated into a query that breaks the using of index for filtering. Instead try parsing for an int userId first (looks like it's a string in your case) and use that parsed int directly for comparison in the query, like this:
if(!int.TryParse(userId, out var intUserId)){
//return or throw exception
}
//here we have an user id of int, use it directly in your query
var user = _context.Users
.Include(e => e.Documents)
.Include(e => e.Positions)
.FirstOrDefault(e => e.Id == intUserId);
That applies similarly to other queries as well.

Related

Entity Framework Create a left outer join that only pulls in the last record

I searched on my particular question and found several close questions, but none that helped after much experimentation.
Given the following code:
var results = context.Shipments.AsNoTracking().Where(x => customerIds.Contains(x.CustomerId.Value) && x.ProcessStageFlag == ProcessStage.Complete)
.GroupJoin(context.Audit.AsNoTracking().Where(x => x.Action != null),
sh => new { im.Shipments.TrackingNumber, im.Shipments.InvoiceNumber },
au => new { rn.TrackingNumber, rn.InvoiceNumber },
(sh, au) => new { Shipments = sh.Shipments, Audit = au })
.SelectMany(x => x.Audit.DefaultIfEmpty(),
(x, y) => new { Shipments = x.Shipments, Audit = y })
.Select(...)
.ToList()
.GroupBy(x => new {x.TrackingNumber, x.InvoiceNumber})
.Select(z => z.FirstOrDefault())
.ToList();
I can't seem to get the last record to be the one I pick up from Audit.
I have tried adding .OrderByDescending(x => x.LastUpdatedOn) to the .GroupJoin as well as the .SelectMany with no affect.
This is the query that I am trying to reproduce in LINQ:
SELECT ship.Invoice_Number, au.Notes, au2.date_added
FROM shipments ship
LEFT OUTER JOIN audit au ON ship.Invoice_Number = au.invoice_number AND au.is_manual_note = 1
LEFT OUTER JOIN audit au2 ON ship.Invoice_Number = au2.invoice_number AND ra2.notes = 'Auto Created' AND au2.date_added = (SELECT MAX(date_added) FROM audit WHERE Invoice_Number = ship.invoice_number AND notes = 'Auto Created')
The audit table has to be joined twice, once to pull the manually entered notes and once to determine when the audit was created. The date_added query is necessary since shipments can move in and out of audit multiple times.
Sammer

Select in multiple tables using EFCore

I have the MySql tables schema below (resumed):
I need to Select only the category data in a Query using EFCore:
List<CategoryViewModel> viewModel = await _context.Category
.Join(_context.Product_Category, c => c.CategoryId, pc => pc.CategoryId, (c, pc) => new { c, pc })
.Join(_context.Product, cpc => cpc.pc.ProductId, p => p.ProductId, (cpc, p) => new { cpc, p })
.Where(cpcp => cpcp.p.EstablishmentId == paramEstablishmentId) //paramEstablishmentId comes via parameter
.Select(vm => new CategoryViewModel()
{
Id = vm.cpc.pc.category.CategortId,
Name = vm.cpc.pc.category.Name,
Image = vm.cpc.pc.category.ImagePath,
Description = vm.cpc.pc.category.Description
})
.ToListAsync();
But this query always result a list with zero models inside. I guarantee there are values in the database to be returned.
Any Ideia what i'm doing wrong?
Many Thanks!
You should use Include()function instead of join. For eg :
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ToList();
Based on #Flyzzx Answer (many thanks, friend), i've modify my query to:
List<CategoryViewModel> viewModel = await _context.Product_Category
.Where(pc => pc.Product.EstablishmentId == EstablishmentId)
.Include(pc => pc.Product)
.Include(pc => pc.Category)
.Select(c => new CategoryViewModel()
{
Id = c.Category.Id,
Name = c.Category.Name,
Image = c.Category.ImagePath,
Description = c.Category.Description
}).Distinct()
.ToListAsync();
Basically, instead of select Categories, now i select Product_Category and use Include to add Products and Categories, making possible to use the Where Clause.

how to optimize mysql query in phalcon

i used this query:
$brands = TblBrand::find(array("id In (Select p.brand_id From EShop\\Models\\TblProduct as p Where p.id In (Select cp.product_id From EShop\\Models\\TblProductCategory as cp Where cp.group_id_1='$id'))", "order" => "title_fa asc"));
if($brands != null and count($brands) > 0)
{
foreach($brands as $brand)
{
$brandInProductCategory[$id][] = array
(
"id" => $brand->getId(),
"title_fa" => $brand->getTitleFa(),
"title_en" => $brand->getTitleEn()
);
}
}
TblBrand => 110 records
TblProduct => 2000 records
TblProductCategory => 2500 records
when i used this code, my site donot show and loading page very long time ...
but when i remove this code, my site show.
how to solve this problem?
The issue is your query. You are using the IN statement in a nested format, and that is always going to be slower than anything else. MySQL will need to first evaluate what is in the IN statement, return that and then do it all over again for the next level of records.
Try simplifying your query. Something like this:
SELECT *
FROM Brands
INNER JOIN Products ON Brand.id = Products.brand_id
INNER JOIN ProductCategory ON ProductCategory.product_id = Products.id
WHERE ProductCategory.group_id_1 = $id
To achieve the above, you can either use the Query Builder and get the results that way
https://docs.phalconphp.com/en/latest/api/Phalcon_Mvc_Model_Query_Builder.html
or if you have set up relationships in your models between brands, products and product categories, you can use that.
https://docs.phalconphp.com/en/latest/reference/model-relationships.html
example:
$Brands = Brands::query()
->innerJoin("Products", "Products.brand_id = Brand.id")
->innerJoin("ProductCategory", "ProductCategory.product_id = Products.id")
->where("ProductCategory.group_id_1 = :group_id:")
->bind(["group_id" => $id])
->cache(["key" => __METHOD__.$id] // if defined modelCache as DI service
->execute();
$brandInProductCategory[$id] = [];
foreach($Brands AS $Brand) {
array_push($brandInProductCategory[$id], [
"id" => $Brand->getId(),
"title_fa" => $Brand->getTitleFa(),
"title_en" => $Brand->getTitleEn()
]);
}

LINQ join and group

How to expand this query:
public Dictionary<int, List<TasksInDeal>> FindAllCreatedTasks()
{
return (from taskInDeal in db.TasksInDeals
where taskInDeal.Date > DateTime.Now && taskInDeal.Date < DateTime.Now.AddDays(7)
group taskInDeal by taskInDeal.CreatedByUserID
into groupedDemoClasses
select groupedDemoClasses).ToDictionary(gdc => gdc.Key, gdc => gdc.ToList());
}
into something like this:
public Dictionary<int, List<TaskForNotification>> FindAllCreatedTasks()
{
return (from taskInDeal in db.TasksInDeals
join user in db.Users on taskInDeal.CreatedByUserID equals user.UserID
where taskInDeal.Date > DateTime.Now && taskInDeal.Date < DateTime.Now.AddDays(7)
group taskInDeal by taskInDeal.CreatedByUserID
into groupedDemoClasses
select new TaskForNotification
{
Email = user.Email,
TaskInDealField1 = taskInDeal.TaskInDealField1,
TaskInDealField2 = taskInDeal.TaskInDealField2,
TaskInDealField3 = taskInDeal.TaskInDealField3,
...
}
).ToDictionary(gdc => gdc.Key, gdc => gdc.ToList());
}
So, to first query I need to join email from other table.
// do the date logic up front, not in the database.
DateTime now = DateTime.Now
DateTime weekFromNow = now.AddDays(7);
// pull the joined rows out of the database.
var rows =
(
from taskInDeal in db.TasksInDeals
where taskInDeal.Date > now && taskInDeal.Date < weekFromNow
join user in db.Users
on taskInDeal.CreatedByUserID equals user.UserID
select new {TaskInDeal = taskInDeal, UserEmail = user.Email}
).ToList();
// shape the rows in memory
Dictionary<int, List<TaskForNotification>> result =
(
from row in rows
let taskForNotification = new TaskForNotification
{
Email = row.UserEmail,
TaskInDealField1 = row.TaskInDeal.TaskInDealField1,
TaskInDealField2 = row.TaskInDeal.TaskInDealField2,
TaskInDealField3 = row.TaskInDeal.TaskInDealField3,
...
}
group taskForNotification by row.TaskInDeal.CreatedByUserID
// without an "into", group by ends the query.
).ToDictionary(g => g.Key, g => g.ToList());
When you group, bear this in mind. Groups in SQL have only keys and aggregates. Groups in LINQ have keys, aggregates and elements! If you ask the database for groups, and then ask for the elements - SQL couldn't provide you with those elements in a single query. You'll wind up automatically repeatedly re-querying using the group's key as a filter.

Linq-to-sql - query is not filtered

I am really new to Linq and am using Linq-to-Sql as follows. However in the following example, my where clause never gets executed and the resultant query attempts to fetch all the records from my table, ignoring even the take method.
Can somebody point out as to what i am doing wrong
var baseQry = db.Table;
baseQry.Where(a => a.tab_id == theId);
baseQry.Select(o => new
{
o.name,
o.display_name,
o.type,
o.info,
time_stamp = (Convert.ToDateTime(o.timestamp).ToLongDateString())
}).Take(10);
baseQry.ToList();
Your second line...
baseQry.Where(a => a.tab_id == theId);
...is essentially a no-op, because the resulting query isn't carried over into your .Select clause.
You need to change it to this:
var baseQry = db.Table;
var results = baseQry
.Where(a => a.tab_id == theId)
.Select(o => new
{
o.name,
o.display_name,
o.type,
o.info,
time_stamp = (Convert.ToDateTime(o.timestamp).ToLongDateString())
})
.Take(10)
.ToList();