LINQ Updating Sort Order to nearest possible value - c#

I have a table with items that are displayed by their SortOrder. The SortOrders are not incremented very well, with values skipping anywhere from 1-100. What I want to do is flip-flop the SortOrder values of two items that are closest together, without necessarily knowing how close together they are. For example:
ItemX.SortOrder = 5;
ItemY.SortOrder = 26;
Assume no items have a sort order between 5-26.
My code needs to switch this to:
ItemX.SortOrder = 26;
ItemY.SortOrder = 5;
For some reason, my code is switching SortOrders with random items, such that:
ItemX.SortOrder = 5;
ItemY.SortOrder = 26;
ItemZ.SortOrder = 34;
Becomes:
ItemX.SortOrder = 34;
ItemY.SortOrder = 26;
ItemZ.SortOrder = 5;
I think its because the results of my query aren't ordered the way I am picturing them. Here is my query code. If anything else is needed, just let me know.
itemToSwitch = DataSource.Items.Where(item => item.SortOrder > currentItem.SortOrder).First();
int? next = itemToSwitch.SortOrder;
int? previous = currentItem.SortOrder;
currentItem.SortOrder = next;
itemToSwitch.SortOrder = previous;
Sorry if my question is difficult to read or jumbled. I'm trying to be as clear as possible.

I'm suspecting you actually want:
var sorted = DataSource.Items.OrderBy(item => item.SortOrder);
If you're reusing it, a .ToList() might help

Why do not use:
var greatesItem = DataSource.Items.OrderBy(item => item.SortOrder).Fisrt();
var smallestItem = DataSource.Items.OrderBy(item => item.SortOrder).Last();
var tempOrder = greatesItem.SortOrder;
greatesItem.SortOrder = smallestItem.SortOrder;
smallestItem.SortOrder = tempOrder;
Then you submit the modification to the database. (I suggest you to do a function with that).

Related

Why are the data records read incorrectly into a List<T>?

Does anyone have an idea why the source code has the following behavior (there are only two data sets after an inner join): The two data sets are read correctly. But now I have the second dataset as result in both datasets. I just can't figure it out.
for (int x = 0; x < ds.Tables[0].Rows.Count; x++)
{
tasks.ID = (int)ds.Tables[0].Rows[x]["ID"];
tasks.UserID = (int)ds.Tables[0].Rows[x]["UserID"];
tasks.Aufgabe = (int)ds.Tables[0].Rows[x]["Aufgabe"];
tasks.Start = (DateTime)ds.Tables[0].Rows[x]["Start"];
tasks.Ende = (DateTime)ds.Tables[0].Rows[x]["Ende"];
tasks.Erledigt = (ds.Tables[0].Rows[x]["Erledigt"] == DBNull.Value) ? DateTime.MinValue : (DateTime)ds.Tables[0].Rows[x]["Erledigt"];
tasks.AufgabenID = (int)ds.Tables[0].Rows[x][6];
tasks.Bezeichnung = ds.Tables[0].Rows[x]["Bezeichnung"].ToString();
tasks.Beschreibung = ds.Tables[0].Rows[x]["Beschreibung"].ToString();
tasks.Zeitrahmen = (int)ds.Tables[0].Rows[x]["Zeitrahmen"];
tasks.Wert = (decimal)ds.Tables[0].Rows[x]["Wert"];
tasks.WertGutschrift = (int)ds.Tables[0].Rows[x]["WertGutschrift"];
tasks.Folgeaufgabe = (int)ds.Tables[0].Rows[x]["Folgeaufgabe"];
taskList.Add(tasks);
}
You are adding the same object several times. Create a new one in every iteration (as first line in the for loop):
Task tasks = new Task();
You set all properties of one object and add it to the list. In the next iteration of the loop, you update the existing object with new values and add it again to the list.
P.S: Don't forget to remove the line, where you are creating the object right now, or you will get a compiler error.

c# collections and re-numbering not working as expected

Hi i'm trying to setup simple test data.
I simply want to take a collection which is smallish and make it bigger by add itself multiple times.
After I;ve added them together i want to re-number the property LineNumber
so that there are no duplicates and that it goes in order. 1,2,3,4....
no matter what i try it doesn't seem to work and i cant see the mistake.
var sampleTemplateLine = dataContext.TemplateFileLines.ToList();
*//tired this doesnt work either*
//List<TemplateFileLine> lineRange = new List<TemplateFileLine>();
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
var allProducts = sampleTemplateLine
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.ToList();
int i = 1;
foreach (var item in allProducts)
{
item.LineNumber = i;
i++;
}
this doesnt seem to work either
//re-number the line number
var total = allProducts.Count();
for (int i =0; i < total; i++)
{
allProducts[i].LineNumber = i+1;
}
PROBLEM: below RETURN 4 when i'm expecting 1
var itemthing = allProducts.Where(x => x.LineNumber == 17312).ToList();
You are adding the same objects multiple times. You wold have to add new objects or clone the ones you have.
The problem is they are pointing the same object. So if you change a property it changes all the pointed objects at the same
You can use Clone method if it exist, if not you can create your own Clone method like in this question.

JSON data List to combo box

I know this has been asked quite a few times and I know ot will be really simple but I'm really new to C# and I'm pulling my hair out because I've been coding (not very well) through the night. I have a class ProcessOrdersActive that I Deserialiaze to details. It's falling over when I try to add ProcessOrderNbr[I] to the combobox.
//Deserialise data
ProcessOrdersActive details = JsonConvert.DeserializeObject<ProcessOrdersActive>(responseBody);
var ordersList = new List<ProcessOrdersActive>();
ordersList.Add(details);
int numofitems = ordersList.Capacity;
txtActiveOrders.Text = numofitems.ToString();
for (int i = 0; i < numofitems; i++)
{
comboBoxOrders.Items.Add (details.ProcessOrderNbr[i]);
}
Many thanks for the responses. I had a good chat with a proficient C# programmer today and here is the solution that he came to. The names are slightly different from the original post.
//Deserialise data & send to DataGrid
ProcessOrderDetails details = JsonConvert.DeserializeObject<ProcessOrderDetails>(responseBody);
//Get number of operations and display to screen
int numofitems = details.MaterialList.Count<Materiallist>();
txtNumOfMaterials.Text = numofitems.ToString();
//Find the last operation
var lastone = details.MaterialList.Last<Materiallist>();
//Create a new material/operation list
var materialList = new List<Materiallist>();
//Add the last operation to the list
materialList.Add(lastone);
//Parse the list to the data grid
dataGridProcessOrderDetails.DataSource = materialList;
You are trying to access an index that may be out of bounds of the array/list.
The number of items should be the Length/Count of the array/List you are accessing.
//Deserialise data
ProcessOrdersActive details = JsonConvert.DeserializeObject<ProcessOrdersActive>(responseBody);
var ordersList = new List<ProcessOrdersActive>();
ordersList.Add(details);
int numofitems = details.ProcessOrderNbr.Count;//If this is a list use Count. If it is an array use Length
txtActiveOrders.Text = numofitems.ToString();
for (int i = 0; i < numofitems; i++) {
comboBoxOrders.Items.Add (details.ProcessOrderNbr[i]);
}

How to write this in better way?

Let's look at this code:
IList<IHouseAnnouncement> list = new List<IHouseAnnouncement>();
var table = adapter.GetData(); //get data from repository object -> DataTable
if (table.Rows.Count >= 1)
{
for (int i = 0; i < table.Rows.Count; i++)
{
var anno = new HouseAnnouncement();
anno.Area = float.Parse(table.Rows[i][table.areaColumn].ToString());
anno.City = table.Rows[i][table.cityColumn].ToString();
list.Add(anno);
}
}
return list;
Is it better way to write this in less code and better fashion (must be :-) )? Maybe using lambda (but let me know how)?
Thanks in advance!
Just FYI, you're never adding the new HouseAnnouncement to your list, and your loop will never execute for the last row, but I'm assuming those are errors in the example rather than in your actual code.
You could do something like this:
return adapter.GetData().Rows.Cast<DataRow>().Select(row =>
new HouseAnnouncement()
{
Area = Convert.ToSingle(row["powierzchnia"]),
City = (string)row["miasto"],
}).ToList();
I usually go for readability over brevity, but I feel like this is pretty readable.
Note that while you could still cache the DataTable and use table.powierzchniaColumn in the lambda, I eliminated that so that you didn't use a closure that wasn't really necessary (closures introduce substantial complexity to the internal implementation of the lambda, so I avoid them if possible).
If it's important to you to keep the column references as they are, then you can do it like this:
using (var table = adapter.GetData())
{
return table.Rows.Cast<DataRow>().Select(row =>
new HouseAnnouncement()
{
Area = Convert.ToSingle(row[table.powierzchniaColumn]),
City = (string)row[table.miastoColumn],
}).ToList();
}
This will add complexity to the actual IL that the compiler generates, but should do the trick for you.
You could do something like this in Linq:
var table = adapter.GetData();
var q = from row in table.Rows.Cast<DataRow>()
select new HouseAnnouncement()
{ Area = float.Parse(row[table.areaColumn].ToString()),
City = row[table.cityColumn].ToString()
};
return q.ToList();
Your "if statement" is not necessary. Your "for loop" already takes care of that case.
Also, your "for loop" will not execute when the number of your Table Rows is 1. This seems like a mistake, and not by design, but I could be wrong. If you want to fix this, just take out the "-1":
for (int i = 0; i < table.Rows.Count; i++)
Well, for one thing, you appear to have an off-by-one error:
for (int i = 0; i < table.Rows.Count - 1; i++)
{
}
If your table has three rows, this will run while i is less than 3 - 1, or 2, which means it'll run for rows 0 and 1 but not for row 2. This may not be what you intend.
Can't go much simpler that one for-loop and no if-statements:
var table = adapter.GetData(); //get data from repository object -> DataTable
IList<IHouseAnnouncement> list = new List<IHouseAnnouncement>(table.Rows.Count);
for (int i = 0; i < list.Length; i++)
{
list[i] = new HouseAnnouncement();
list[i].Area = float.Parse(table.Rows[i][table.areaColumn].ToString());
list[i].City = table.Rows[i][table.cityColumn].ToString();
}
return list;
It takes more characters than linq-version, but is parsed faster by programmer's brain. :)
Readability is, to me, preferable to being succinct with your code--as long as performance is not a victim. Also, I am sure that anyone who later has to maintain the code will appreciate it as well.
Even when I am maintaining my own code, I don't want to look at it, say a couple of months later, and think "what the hell was I trying to accomplish"
I might do something like this:
var table = adapter.GetData(); //get data from repository object -> DataTable
return table.Rows.Take(table.Rows.Count-1).Select(row => new HouseAnnouncement() {
Area = float.Parse(row[table.powierzchniaColumn].ToString()),
City = row[table.miastoColumn].ToString()
}).ToList();

How to select min and max values of a column in a datatable?

For the following datatable column, what is the fastest way to get the min and max values?
AccountLevel
0
1
2
3
Easiar approach on datatable could be:
int minLavel = Convert.ToInt32(dt.Compute("min([AccountLevel])", string.Empty));
int minAccountLevel = int.MaxValue;
int maxAccountLevel = int.MinValue;
foreach (DataRow dr in table.Rows)
{
int accountLevel = dr.Field<int>("AccountLevel");
minAccountLevel = Math.Min(minAccountLevel, accountLevel);
maxAccountLevel = Math.Max(maxAccountLevel, accountLevel);
}
Yes, this really is the fastest way. Using the Linq Min and Max extensions will always be slower because you have to iterate twice. You could potentially use Linq Aggregate, but the syntax isn't going to be much prettier than this already is.
Use LINQ. It works just fine on datatables, as long as you convert the rows collection to an IEnumerable.
List<int> levels = AccountTable.AsEnumerable().Select(al => al.Field<int>("AccountLevel")).Distinct().ToList();
int min = levels.Min();
int max = levels.Max();
Edited to fix syntax; it's tricky when using LINQ on DataTables, and aggregating functions are fun, too.
Yes, it can be done with one query, but you will need to generate a list of results, then use .Min() and .Max() as aggregating functions in separate statements.
This worked fine for me
int max = Convert.ToInt32(datatable_name.AsEnumerable()
.Max(row => row["column_Name"]));
The most efficient way to do this (believe it or not) is to make two variables and write a for loop.
var answer = accountTable.Aggregate(new { Min = int.MinValue, Max = int.MaxValue },
(a, b) => new { Min = Math.Min(a.Min, b.Field<int>("AccountLevel")),
Max = Math.Max(a.Max, b.Field<int>("AccountLevel")) });
int min = answer.Min;
int max = answer.Max;
1 iteration, linq style :)
another way of doing this is
int minLavel = Convert.ToInt32(dt.Select("AccountLevel=min(AccountLevel)")[0][0]);
I am not sure on the performace part but this does give the correct output
var min = dt.AsEnumerable().Min(row => row["AccountLevel"]);
var max = dt.AsEnumerable().Max(row => row["AccountLevel"]);
Session["MinDate"] = dtRecord.Compute("Min(AccountLevel)", string.Empty);
Session["MaxDate"] = dtRecord.Compute("Max(AccountLevel)", string.Empty);
Performance wise, this should be comparable. Use Select statement and Sort to get a list and then pick the first or last (depending on your sort order).
var col = dt.Select("AccountLevel", "AccountLevel ASC");
var min = col.First();
var max = col.Last();
I don't know how my solution compares performance wise to previous answers.
I understand that the initial question was: What is the fastest way to get min and max values in a DataTable object, this may be one way of doing it:
DataView view = table.DefaultView;
view.Sort = "AccountLevel";
DataTable sortedTable = view.ToTable();
int min = sortedTable.Rows[0].Field<int>("AccountLevel");
int max = sortedTable.Rows[sortedTable.Rows.Count-1].Field<int>("AccountLevel");
It's an easy way of achieving the same result without looping. But performance will need to be compared with previous answers. Thought I love Cylon Cats answer most.

Categories