In my .Net application, EntityFramework 6 is used and I am writing a logic in C# to update one of the column values of the ORDERTABLE to Yes or No.
In ORDERTABLE, for a Single TranID there will be one or more OrderNumber. I need to update the last OrderNumber (i.e., MAX(OrderNumber)) value of a TranID to 'NO' and for remaining OrderNumber value will be 'YES'.
Below SQL query is giving me the expected result but I am not sure of converting this into LINQ to Entities code logic.
UPDATE SC
SET ORDERVALUE = CASE WHEN SC1.ORDERNUMBER IS NULL THEN 'YES' ELSE 'NO' END
FROM ORDERTABLE SC
LEFT JOIN (SELECT MAX(ORDERNUMBER) ORDERNUMBER, SID FROM ORDERTABLE WHERE STATUS ='A' GROUP BY SID) SC1
ON SC.ORDERNUMBER = SC1.ORDERNUMBER AND SC.SID = SC1.SID
WHERE SC.STATUS ='A' AND SC.SID IN (SELECT ID FROM ORDERMASTER(NOLOCK) WHERE MID = variablename)
Select Query:
SELECT
ORDERVALUE = CASE WHEN SC1.ORDERNUMBER IS NULL THEN 'YES' ELSE 'NO' END,
SC.*
FROM ORDERTABLE SC
LEFT JOIN (SELECT MAX(ORDERNUMBER) ORDERNUMBER, SID FROM ORDERTABLE WHERE STATUS ='A' GROUP BY SID) SC1
ON SC.ORDERNUMBER = SC1.ORDERNUMBER AND SC.SID = SC1.SID
WHERE SC.STATUS ='A' AND SC.SID IN (SELECT ID FROM ORDERMASTER(NOLOCK) WHERE MID = variablename)
In C#, LINQ to Entities code should looks like somewhat similar to below first format (LINQ Method) but not like the second one(LINQ query).
//1. LINQ Method
using (var context = new ProductDBEntities())
{
dbContextTransaction = context.Database.BeginTransaction();
ORDERDETAILS od = context.ORDERDETAILS.Single(G => G.ID == 1);
od.OrderNumber = OrdNumber;
od.LastModifiedBy = createdBy;
od.LastModifiedTS = DateTime.UtcNow;
context.SaveChanges();
}
//2. LINQ Query
using (var context = new ProductDBEntities())
{
var query = from st in context.ORDERTABLE
where ...
select st;
var ORDERTABLE = query.FirstOrDefault<ORDERTABLE >();
}
Related
I am trying replicate the SQL below using LINQ and Entity Framework and cannot figure out how this should be written.
My simplistic LINQ version does a query per table
public IActionResult Index()
{
dynamic view = new ExpandoObject();
view.AppUsers = Context.AppUsers.Count();
view.CustomerShops = Context.CustomerShops.Count();
view.FavouriteOrders = Context.FavouriteOrders.Count();
view.Items = Context.Items.Count();
view.ItemVariations = Context.ItemVariations.Count();
view.MenuCategories = Context.MenuCategories.Count();
view.MenuCategoryProducts = Context.MenuCategoryProducts.Count();
view.Orders = Context.Orders.Count();
view.Products = Context.Products.Count();
view.ProductVariations = Context.ProductVariations.Count();
view.Shops = Context.Shops.Count();
view.Staffs = Context.Staffs.Count();
return View(view);
}
I use this pattern from time to time to for reporting on my column counts and thought this should be easy to do in LINQ, but no luck so far.
This pure SQL UNION would only generate 1 SQL request, instead of a request per table.
select * from (
select 'asp_net_roles' as type, count(*) from asp_net_roles
union
select 'asp_net_user_roles' as type, count(*) from asp_net_user_roles
union
select 'asp_net_users' as type, count(*) from asp_net_users
union
select 'app_users' as type, count(*) from app_users
union
select 'shops' as type, count(*) from shops
union
select 'staffs' as type, count(*) from shops
union
select 'items' as type, count(*) from items
union
select 'item_variations' as type, count(*) from item_variations
union
select 'products' as type, count(*) from products
union
select 'product_variations' as type, count(*) from product_variations
union
select 'menu_categories' as type, count(*) from menu_categories
) as counters
order by 1;
I saw a partial implementation [linq-group-by-multiple-tables] (https://stackoverflow.com/a/3435503/473923) but this is based of grouping data.
FYI: I'm new to C#/Linq, so sorry if this seams obvious.
Use the this code from my answer
And fill ExpandoObject with result:
var tablesinfo = Context.GetTablesInfo();
var expando = new ExpandoObject();
if (tablesinfo != null)
{
var dic = (IDictionary<string, object>)expando;
foreach(var info in tablesinfo)
{
dic.Add(info.TableName, info.RecordCount);
}
}
Idea is that you can UNION counts if you group entities by constant.
Schematically function builds the following IQueryable Expression:
var tablesinfo =
Context.AppUsers.GroupBy(x => 1).Select(g => new TableInfo{ TableName = "asp_net_roles", RecordCount = g.Count() })
.Concat(Context.MenuCategories.GroupBy(x => 1).Select(g => new TableInfo{ TableName = "menu_categories", RecordCount = g.Count() }))
.Concat(Context.Items.GroupBy(x => 1).Select(g => new TableInfo{ TableName = "items", RecordCount = g.Count() }))
....
There is nothing wrong with your LINQ query. It's very acceptable approach. However it's not the most efficient.
There is no need to fetch count from individual tables one by one. You can get the counts from all the tables at once using the System tables Sys.Objects and Sys.Partitions. Just try running this query in your database.
SELECT A.Name AS TableName, SUM(B.rows) AS RecordCount
FROM sys.objects A INNER JOIN sys.partitions B
ON A.object_id = B.object_id
WHERE A.type = 'U' AND B.index_id IN (0, 1)
GROUP BY A.Name
For quick response and cleaner code, you can store this SQL query in a string variable, and run the LINQ
var result = dataContext.ExecuteQuery<YOUR_MODEL_CLASS>
(your_string_query);
I would put something like this:
Dictionary<string, int> view = new() {
new() {'asp_net_roles', Context.AppUsers.Count() },
...
}
return View(view);
maybe not the most pure way, but does the job (unless I misunderstood what you try to accomplish)
There are tables Subject, Student and SubjectEnrolled
Subject table have two columns SubjectId and SubjectName
SubjectEnrolled table also have two column StudentID(foreign key to StudentTable) and SubjectId(foreign key to Subject)
I want to convert this SQL query
SELECT SubjectName
FROM Subject
WHERE SubjectId IN
(
SELECT SubjectId
FROM SubjectEnrolled
WHERE StudentID=7
)
Into a Linq or Lamda expression
using (var db = new DbContext())
{
var res = from r in db.Subjects.....
}
1 - SQL : use inner join instead IN :
SELECT SubjectName FROM Subject sub
INNER JOIN SubjectEnrolled subEn on sub.SubjectId = subEn.SubjectId
WHERE subEn.StudentID = 7
2 - Linq Query Join:
var res = (from sub in db.Subjects
join subEn in db.SubjectEnrolleds on sub.SubjectId equals subEn.SubjectId
where subEn.StudentID = 7).ToList();
I hope you find this helpful.
I have a ticketing system that I am trying to get some basic information of the last ticket note added, based on what tickets are still open.
Here are the tables summarized, including extra columns that I'm not querying:
Ticket table
TicketId
CustomerId
DateIn
CallNature
OpenClosed
TicketDetails table
TicketDetailsId
TicketId
TicketNote
DateLogged
Here is the SQL query:
SELECT
t.TicketId,
t.CustomerId,
t.DateIn,
(SELECT TOP 1 td.DateLogged
FROM TicketDetails td
WHERE td.TicketId = t.TicketId
ORDER BY td.DateLogged DESC) DateLogged
FROM
Tickets t
WHERE
t.OpenClosed = 1
Here is my current Linq query:
var result = from t in ef.Tickets
where t.OpenClosed == true
select new
{
TicketId = t.TicketId,
CustomerId = t.CustomerId,
DateIn = t.DateIn,
DateLogged = (from td in ef.TicketDetails
where td.TicketId == t.TicketId
orderby td.DateLogged descending
select td.DateLogged).Take(1)
};
Here is a result sample of the Linq query:
TicketId = 11000
CustomerId = 4622
DateIn = 2018-01-25T00:00:00
DateLogged = 0001-01-01T00:00:00
should be:
TicketId = 11000
CustomerId = 4622
DateIn = 2018-01-25T00:00:00
DateLogged = 2018-12-12T13:32:42
I don't have all the fields in the example but they are irrelevant to the question.
When I run the SQL query, the results are as expected. When I run the Linq query, all fields are populated except for the DateLogged, it keeps returning default value (0001-01-01).
I have confirmed that each Ticket has at least 1 TicketDetail to return.
Final solution thanks to Neil's reference to deferred execution is:
var result = (from t in ef.Tickets
where t.OpenClosed == true
select new {
TicketId = t.TicketId,
CustomerId = t.CustomerId,
DateIn = t.DateIn,
DateLogged = (from td in ef.TicketDetails
where td.TicketId == t.TicketId
orderby td.DateLogged descending
select td.DateLogged).Take(1).FirstOrDefault()
}).ToList();
And a link to a post explaining difference between Linq query execution.
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/query-execution
I know the SQL I want to produce:
SELECT qdt, pbn, cid, pid, SUM(Amount) AS TotalAmount
FROM SomeDb.SomeTable
WHERE Status = 'Open'
GROUP BY cid, pid, qdt, pbn
ORDER BY qdt
I have LINQ, that I was hoping would produce something as clean as the above SQL:
var query = (
from someTable in SomeTable
where someTable.Status == "Open"
group someTable by new { someTable.cid, someTable.pid } into grouped
select new
{
lid = grouped.FirstOrDefault().lid,
qdt = grouped.FirstOrDefault().qdt,
pbn = grouped.FirstOrDefault().pbn,
cid = grouped.FirstOrDefault().cid,
cn = grouped.FirstOrDefault().cn,
pid = grouped.FirstOrDefault().pid,
amount = grouped.Sum(o => o.Amount),
Status = grouped.FirstOrDefault().Status
});
But that produces many lines of nasty SQL. Each grouped value ends up getting its own SELECT statement within the query, like this, for qdt:
SELECT [t5].[qdt]
FROM (
SELECT TOP (1) [t4].[qdt]
FROM [SomeDb].[SomeTable] AS [t4]
WHERE ([t1].[cid] = [t4].[cid]) AND ([t1].[pid] = [t4].[pid]) AND ([t4].[Status] = #p0)
) AS [t5]
) AS [qdt]
Is there a way to change the LINQ to produce the simpler SQL?
There are two issues I see with your LINQ attempt. For starters, you are not grouping by the same fields in the SQL query as in your LINQ. Then, you are using FirstOrDefault on the results of the group instead of selecting the group's key data.
Your query should look like this:
var query =
(
from someTable in SomeTable
where someTable.Status == "Open"
group someTable by new
{
someTable.lid,
someTable.qdt,
someTable.pbn,
someTable.cid,
someTable.cn,
someTable.pid,
someTable.Status,
} into grouped
select new
{
lid = grouped.Key.lid,
qdt = grouped.Key.qdt,
pbn = grouped.Key.pbn,
cid = grouped.Key.cid,
cn = grouped.Key.cn,
pid = grouped.Key.pid,
amount = grouped.Sum(o => o.Amount),
Status = grouped.Key.Status
}
);
I cannot test right now whether it will generate the exact same SQL though.
You don't seem to have all the grouped by columns in the desired sql in your linq;
var query = (
from someTable in SomeTable
where someTable.Status == "Open"
group someTable by new { someTable.cid, someTable.pid, someTable.qdt,someTable.pbn, someTable.Status } into grouped
select new
{
qdt = grouped.FirstOrDefault().qdt,
pbn = grouped.FirstOrDefault().pbn,
cid = grouped.FirstOrDefault().cid,
pid = grouped.FirstOrDefault().pid,
Status = grouped.FirstOrDefault().Status,
amount = grouped.Sum(o => o.Amount)
});
I can't really test it but that looks a little more like the sql you are trying to replicate in linq.
I have some models (restaurants, shops, products), and i want to select records count for multiple models in a single linq query.
I know how it should be in sql, but i don't know how to translate it in linq:
select
(select count(*) from restaurants) as restaurantsCount,
(select count(*) from shops) as shopsCount,
(select count(*) from products) as productsCount
from
dual
Considering dual is a dummy table with single row:
var result = new
{
RestaurantsCount = context.Restaurants.Count(),
ShopsCount = context.Shops.Count(),
ProductsCount = context.Products.Count()
};
Single query solution:
var result = from dummyRow in new List<string> { "X" }
join product in context.products on 1 equals 1 into pg
join shop in context.shops on 1 equals 1 into sg
join restaurant in context.restaurants on 1 equals 1 into rg
select new
{
productsCount = pg.Count(),
shopsCount = sg.Count(),
restaurantsCount = rg.Count()
};