Retrieve multiple columns from last Row in Table using Linq - c#

I am new to C# LINQ Queries.
I wish to access the last record in a table and retrieve the values of 2 columns from it.
Accessing the last row is not proving difficult as I can sort with descending order on a unique integer key where the greater value represents a later addition, but I am not sure how I can return 2 different values(integer and string) using a single query.
Would I have to perform 2 different queries, each querying for one item and return that, or would it be more efficient to have the whole lot in a string, and parse the string elsewhere?
This is what I have so far. I am not certain which way would be better.
public IEnumerable GetDeployHistoryTableDate(int nodeId)
{
if (nodeId == 0)
{
return new List<object>();
}
using (var session = sessionFactoryProject.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var items = session.Query<DeployHistory>()
.Where(i => i.NodeId == nodeId)
.OrderByDescending(i => i.Id)
.Select(i => new
{
i.EndTime,
i.Result
})
.First().ToString();
transaction.Commit();
return items;
}
}
}

Related

this code takes a 2hrs to compare and sort 20,000 items each, is there a better way to write this c# code

I am trying to sort all the updated item in DataTableA, by coloring the item that has not been completely updated, and removing the item that has been updated completely from the DataTable. both The item that has been updated completely and the incomplete updated item are in "managed" table in the database, the discharge date will be null if it has not been completely updated.
Below code works but it can take all day for the page to run. This is C# webform.
The code below is writing on my code behind file:
foreach (GridDataItem dataItem in RadGrid1.Items)
{
var panu = dataItem["Inumber"];
var panum = panu.Text;
var _cas = db.managed.Any(b =>
b.panumber == panum && b.dischargedate != null);
var casm = db.managed.Any(b =>
b.panumber == panum && b.dischargedate == null);
if (_cas == true)
{
dataItem.Visible = false;
}
if (casm == true)
{
dataItem.BackColor = Color.Yellow;
}
}
As mentioned in the comment, each call to db.managed.Any will create a new SQL query.
There are various improvements you can make to speed this up:
First, you don't need to call db.managed.Any twice inside the loop, if it's checking the same unique entity. Call it just once and check dischargedate. This alone with speed up the loop 2x.
// one database call, fetching one column
var dischargedate = db.managed
.Select(x => x.dischargedate)
.FirstOrDefault(b => b.panumber == panum);
var _cas = dischargedate != null;
var casm = dischargedate == null;
If panumber is not a unique primary key and you don't have a sql index for this column, then each db.managed.Any call will scan all items in the table on each call. This can be easily solved by creating an index with panum and dischargedate, so if you don't have this index, create it.
Ideally, if the table is not huge, you can just load it all at once. But even if you have tens of millions of records, you can split the loop into several chunks, instead of repeating the same query over and over again.
Consider using better naming for your variables. _cas and casm are a poor choice of variable names.
Pro tip: Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live.
So if you don't have hundreds of thousands of items, here is the simplest fix: load panumber and discharge values for all rows from that table into memory, and then use a dictionary to instantly find the items:
// load all into memory
var allDischargeDates = await db.managed
.Select(x => new { x.panumber, x.dischargedate })
.ToListAsync(cancellationToken);
// create a dictionary so that you can quickly map panumber -> dischargedate
var dischargeDateByNumber = dbItems
.ToDictionary(x => x.panumber, x => x.dischargedate);
foreach (var dataItem in RadGrid1.Items)
{
var panu = dataItem["Inumber"];
var panum = panu.Text;
// this is very fast to check now
if (!dischargeDateByNumber.TryGetValue(panum, out DateTime? dischargeDate))
{
// no such entry - in this case your original code will just skip the item
return;
}
if (dischargeDate != null)
{
dataItem.Visible = false;
}
else
{
dataItem.BackColor = Color.Yellow;
}
}
If the table is huge and you only want to load certain items, you would do:
// get the list of numbers to fetch from the database
// (this should not be a large list!)
var someList = RadGrid1
.Items
.Select(x => x["Inumber"].Text)
.ToList();
// load these items into memory
var allDischargeDates = await db.managed
.Where(x => someList.Contains(x.panumber))
.Select(x => new { x.panumber, x.dischargedate })
.ToListAsync(cancellationToken);
But there is a limit on how large someList can be (you don't want to run this query for a list of 200 thousand items).
Well, 900 items might be worth simply fetching to a list in memory and then process that. It will definitely be faster, although it consumes more memory.
You can do something like this (assuming the type of managed is Managed):
List<Managed> myList = db.managed.ToList();
That will fetch the whole table.
Now replace your code with:
foreach (GridDataItem dataItem in RadGrid1.Items)
{
var panu = dataItem["Inumber"];
var panum = panu.Text;
var _cas = myList .Any(b =>
b.panumber == panum && b.dischargedate != null);
var casm = myList .Any(b =>
b.panumber == panum && b.dischargedate == null);
if (_cas == true)
{
dataItem.Visible = false;
}
if (casm == true)
{
dataItem.BackColor = Color.Yellow;
}
}
You should see a huge performance approvement.
Another thing: You don't mention what database you're using, but you should make sure the panumber column is properly indexed.

Iterate over an IQueryable without calling ToList()

I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}

Storing results of multiple linq queries in IQueryable

I was wondering if it was possible to store the results of multiple linq queries in a single IQueryable statement?
I have a query which I use in a foreach:
//Where OnDemandHistory is the table
IOrderedQueryable<OnDemandHistory> A;
foreach (int id in machineID)
{
A = OnDemandHistory.Where(c => c.MachineID == id).OrderByDescending(c => c.ODHisDate);
// I want to Order all results before writing to the table
foreach(var entry in A)
{
// I add to a table based on all entries found in A
}
}
I am trying to get all entries where the machine ID match. The no. of MachineID's is varying (based on the user).
I was wondering if I can do a OrderByDescending after I have stored all the results from the query but before adding to the table.
I know due to the inner foreach loop that it won't happen, however when I try to do this:
foreach (int id in machineID)
{
A = OnDemandHistory.Where(c => c.MachineID == id).OrderByDescending(c => c.ODHisDate);
// I want to Order all results before writing to the table
}
foreach(var entry in A)
{
// I add to a table based on all entries found in A
}
I get a local variable A uninitialized error,
How would I go about solving this?
Thanks in advance
You can do it much simpler by using the Contains statement:
var result = OnDemandHistory.Where(c => machineID.Contains(c.MachineID))
.OrderByDescending(c => c.ODHisDate);
The error is caused because as the final result of your first query produces only the result of the last value of machineID this may result in either a null result or an uninitialisedvalue of A, so A needs to be initialised. Also, I suspect A could be a simple list.
You need something like:
A = new List<OnDemandHistory>();
foreach (int id in machineID)
{
A.AddRange(OnDemandHistory
.Where(c => c.MachineID == id).OrderByDescending(c => c.ODHisDate).ToList());
}
// order A here
Then run your second loop having checked that A has rows. However, I suspect there are smarter ways in LINQ of concatenating the machineID part of the query as a single LINQ statement.

Is there a faster way to do this LINQ?

I have a loop inside my program, which loops through thousands of object to find the right one with particular id.
is there any better and faster way than this
int id;
SPList list = SPContext.Current.Web.Lists.TryGetList("DataLibrary");
IEnumerable<SPListItem> _dataitems = list.Items.OfType<SPListItem>();
foreach (SPListItem item in _dataextantitems)
{
if (item.ID == id)
{
title= item.Title;
}
}
Use the GetItemById of SPList.
var title = SPContext.Current.Web.Lists["DataLibrary"].GetItemById(id).Title;
If your list has a lot of columns, and you want to avoid pulling them all down, you can pull down just the Title column instead:
var title = SPContext.Current.Web.Lists["DataLibrary"]
.GetItemByIdSelectedFields(id, "Title").Title;
Now if you really want to use LINQ here you could use LINQ to Sharepoint, but it's not actually going to simplify the code a ton. After using SPMetal.exe to generate a file based on your lists, you'd be able to write:
using(var context = new YourContextNameHere(SPContext.Current.Site.Url))
{
var title = context.DataLibrary
.Where(item => item.ID == id)
.Select(item => item.Title)//to avoid pulling down other columns
.First();
}
Make sure your list is sorted. Then you can use the BinarySearch method of the list or write your own implementation. If not you can shorten your code using linq.
var itemToLookup = list.Items.OfType<SPListItem>().FirstOrDefault(x => x.ID == id);
if (itemToLookup != null)
{
//...
}

Simplest way to check db values against values in an array

I'm looping through the items in my database using C# .NET and I'm attempting to display different data dependant on if a column value matches any of the values in an array. Because my array could potentially have hundreds of values, I'd rather not create hundreds of different IF statements, if possible. Is there a simpler way to achieve this?
Here's some example code, where "Items" is my db data and "Categories" is a column of said data:
var categoryList = new List<int> { 1, 2, 3, 4 };
foreach(var item in Items){
if(item.Categories.Any(x => #categoryList.Equals(x))){
<p>Yes</p>
}else{
<p>No</p>
}
}
The answer I give is based on the answer of this question. I modified the code to your situation.
foreach(var item in Items)
{
bool hasCategory = categoryList.Any(x => item.Categories.Any(c => c.Id == x));
}
or for larger collections (performance-wise):
bool hasCategory = item.Categories.Select(c => c.Id)
.Intersect(categoryList)
.Any();
Edit:
At first I thought item.Categories was a collection of IDs or something but then I started doubting. If item.Categories is just a single integer, following code will work:
foreach(var item in Items)
{
if(categoryList.Any(x => x == item.Categories))
<p>Yes</p>
else
<p>No</p>
}

Categories

Resources