Sorted and indexed WinForms ListBox items - c#

There is Client app with ListBox containing Records sorted by Record Time attribute. On application start Client loads Records from server and start listening to server updates. When server inform Client about new Record Client add Record to ListBox (theoretically with Time before last showed Recort Time). When Server inform about update or delete Client find message by ID and update or delete it (Theoretically when Time of Record is changed order of Records in ListBox must be changed).
I guess somethink like sorted dictionary with value time comparator is required.
public partial class RecordsForm : Form
private System.Windows.Forms.ListBox recordsListBox;
peivate SortedDictionary<long, Record> recordsDictionary;
public RecordsForm()
{
InitializeComponent();
// this is not working because comparer must compare keys
recordsDictionary = new SortedDictionary<long, Record>(new RecordComparer());
var recordsBbinding = new BindingSource();
recordsBbinding.DataSource = recordsDictionary;
recordsListBox.DataSource = recordsBbinding;
}
public HandleCreateUpdate(Record record)
{
recordsDictionary[record.Id] = record;
}
public HandleDelete(Record record)
{
if (recordsDictionary.ContainsKey(record.Id))
{
recordsDictionary.Remove(record.Id);
}
}
}
class Record {
public long Id { get; set; }
public DateTime Time { get; set; }
public String Title { get; set; }
}
class RecordComparer : Comparer<Record>
{
public override int Compare(Record left, Record right)
{
return left.Time.CompareTo(right.Time);
}
}
Or exists another pattern used for something like this?
EDIT: Added screen
List of Records is always sorted by Time desc. I want to synchronize this list across clients. When one edit/add/delete record other clients may reflects changes without reloading entire list or iterating over all items.

Related

Index was outside the bounds of the array error after a long idle time

I have class like this that reads the data from database once and keeps it in memory.
public class PlanetService
{
private List<Planet> planets = new List<Planet>();
public List<Planet> FindAll()
{
if (!planets.Any())
{
planets.AddRange(GetAllPlanetsFromDataBase());
}
return planets;
}
public List<Planet> FindByName(string name)
{
if (!planets.Any())
{
planets.AddRange(GetAllPlanetsFromDataBase());
}
return planets.Where(x => x.Name == name).ToList();
}
}
public class Planet
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Lookup
{
public Lookup(string value, string label)
{
Value = value;
Label = label;
}
public string Value { get; set; }
public string Label { get; set; }
}
This is being used as follows
var lookupList = planetService.FindAll().Select(x => new Lookup(x.Id.ToString(), x.Name)).ToList();
This works as expected initially. But every now and then, usually after a long idle time, the following error occurs
[IndexOutOfRangeException: Index was outside the bounds of the
array.] System.Collections.Generic.Enumerator.MoveNext()
System.Linq.WhereSelectListIterator'2.MoveNext()
System.Collections.Generic.List'1..ctor(IEnumerable'1 collection)
System.Linq.Enumerable.ToList(IEnumerable'1 source)
There is always planets in the database. And even if not it should return an empty Lookup list and shouldn't be throwing an IndexOutOfRangeException, right?
Once this error occurs, subsequent page refreshes keeps showing this error and won't go away for sometime.
Since other database calls are working I assume this is not connection related.
This happens in production only, I am unable to recreate the issue in the dev environment yet.
This exception in my experience tends to come when you try to Serialize massive amounts of data, usually massive single columns from the database.
Are any of the columns you're retrieving from the database containing abnormally large amounts of data?
We had a REST API which had this same exception where the cause was a column in the DB which had grown to GB's in size due to a self referencing bug.

How to update DataGridView cell in c#

I have a DataGridView in c# WinForms
In this form I fill a big DataGridView with data, the process behind takes long time.
anyway, my issue is that I want to update one cell in that DataGridView based on criteria
DataGridView has 4 columns, StNo, StName, StAge, StMark.
I want to search for StNo = 123 and update their StMark to be 68
I tried the following but does not work
grd1.Rows.Cast<DataGridViewRow>().Where(x => x.Cells["StNo"].Value.ToString() == "123")["StMark"] = 68;
How to do that?
DataGridView is a winforms control which responsible for displaying given records in UI and provide user's input to the underlying records via different events.
So instead of updating values via DataGridView - update underlying records.
public class Student
{
public int Number { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Mark { get; set; }
}
public class MyForm
{
public readonly BindingList<Student> _sutdents;
public MyForm()
{
_students = new BindingList<Student>();
myDataGridView.DataSource = _students;
}
private void AddStudentButton_Click(object sender, EventArgs args)
{
var student = new Student
{
Number = int.Parse(textboxNumber.Text),
Name = textboxName.Text,
Name = int.Parse(textboxAge.Text),
Name = int.Parse(textboxMark.Text),
};
_students.Add(student);
}
// After button click you should see that new student appears in the DataGridView
// Now you can update existing students from code without "touching" DataGridView
private void UpdateMarkButton_Click(object sender, EventArgs args)
{
var studentNumber = int.Parse(textboxNewMarkStudentNumber.Text);
var newMark = int.Parse(textboxNewMark.Text);
var student = _students.FirstOrDefault(s => s.Number = studentNumber);
if (student != null)
{
student.Mark = newMark;
}
}
}
Your life, made easy:
add a new DataSet file to your project, open it, name it StudentDataSet in the property grid
right click, add a new DataTable to the surface, name it Students
add columns of StNo (make it an int in the property grid), StName, StAge (int), StMark (int)
right click StNo and make it a primary key
save
switch to the form designer, open the Data Sources panel (view menu.. other windows)
drag the node representing the table, onto the form. A datagridview appears
Now the code you need for updating a Mark is:
var s = studentDataSet.Students.FindByStNo(123);
if(s!=null)
s.StMark = 99;
Yes, that's all you need (plus you need to load the table with student data)
If these students come out of a database, you can make your life easier in the second step by not adding a DataTable but instead adding a TableAdapter and setting up the db connection/query that pulls them out of the db..

EntityFramework One to Many virtual list empty after save

I have encountered this weird behavior I have not seen in other EF projects. There are 3 classes in particular project; File, Record and Element. First I pull the file from Context, then I add Records to the File, then I add Elements to the Record, then Save. Pretty basic stuff right?
However, after I save, Record.Elements is empty; 16 before the save and 0 after. The elements get saved to the database, it's just the virtual list that get's emptied.
Also weirdly, if I query for the elements, the come back (see below).
I do have LazyLoadingEnabled = true if that matters.
public class File
{
public int Id{get;set;}
public virtual List<Records> Records{get;set;}
}
public class Record
{
public int FileId{get;set;}
public int Id{get;set;}
[ForeignKey("FileId")]
public virtual File File{get;set;
public virtual List<Element> Elements{get;set;}
}
public class Element
{
[ForeignKey("Record")]
public int FileId{get;set;}
[ForeignKey("Record")]
public int RecordId{get;set;}
public string ElementName{get;set;}
public virtual Record Record{get;set;}
}
So this is sorta the flow of my code, and the results of Record.Elements
void DoStuff(DbContext context, int fileId)
{
var file = context.Files.FirstOrDefault(file=>file.Id = fileId);
Record record;
file.Records.Add(record = new Record{
File = file,
...
});
for (var i = 0;i < 17; i++)
{
record.Elements.Add(new Element{
Record = record,
...
});
}
// Record.Elements.Count = 16
context.SaveChanges();
// Record.Elements.Count = 0
var elements = Current.Context.FileElements.Where(e => e.RecordId == fileRecord.Id).ToList();
// Record.Elements.Count = 16 again (with the lazy loading string from hell)
}
I need to figure out why the list is being emptied, because without the elements my class is useless, and performing that query I don't feel should be necessary. I don't understand what is going on.
**7/6 Update: still have the problem, I found that if I save after adding the record to file and before adding element to the record it behaves as expected and the elements list is no longer emptied.

Wpf - How to control observable collection updates

In the parent there is a Observable Collection PendingPayment that has a list of all pending payments of sales with a column amount paid.
Then the user can select a particular sale and open it in new child window.
The thing thats going wrong is if the user just edits the text box paid amount in child window and closes the window without saving the new paid amount to database,the observable collection containing Amount paid column in the parent window gets updated.
What I want is it the collection to get updated only when the values are updated in the database.
This can be achieved by creating a copy of your sale object when the user select it in the list, and then using this copy as the view model of your child view.
You will then be able to set the new values in the original object from your list only once the save button has been clicked and the database update succeed.
An other way to proceed if you need to edit only few of the object properties would be to create and editor object and use it as the child window's view model.
Something like this :
public class Sale
{
public int PaidAmount { get; set; }
public int Some { get; set; }
public int More { get; set; }
public int Properties { get; set; }
}
public class SaleEditor
{
private Sale _sale;
public int PaidAmount { get; set; }
public SaleEditor(Sale sale)
{
_sale = sale;
PaidAmount = sale.PaidAmount;
}
public void Save()
{
// update your data here
_sale.PaidAmount = PaidAmount;
}
}
If you need your original object to update the database, then the save method could first update the object and the revert the changes if DB update failed :
public void Save()
{
var oldAmount = _sale.PaidAmount;
_sale.PaidAmount = PaidAmount;
if (!SalesDB.Update(_sale))
_sale.PaidAmount = oldAmount;
// you could also read back the value from DB
}
Whenever possible (I've never see a reason why it cannot),for listing purpose use proxy or flatted objects, you can implement this using projections query. Then user select an item from a list and the only thing you need to grab is a key to load the full object with its required object graph as the use case might dictate.
Here is a sample implementation using Entity Framework and c# lambda expressions:
Using anonymous object:
var anonymousListProjection = DbContext.PendingPayments.Select( pp=>
new { pp.Order, pp.Amount})
Using a hardcoded proxy:
var hardcodedListProjection = DbContext.PendingPayments.Select( pp=>
new PendingPaymentProxy { Order = pp.Order, Amount = pp.Amount})
//To return an observable:
var observableColl = new ObservableCollection<PendingPaymentProxy>
(hardcodedListProjection.Tolist());
public class PendingPaymentProxy
{
public string Order { get; set; }
public decimal Amount{ get; set; }
}
Apart from avoiding possibles performance problems due to unintentional loading real objects, this way you only have to worry for your list when the user do save in the detail view.

RavenDB: Asynchronous SaveChanges affecting later updates?

As part of learning RavenDB, I am trying to update a collection of stocks based on a list I download nightly.
I have a Stock class where Id is the stock symbol:
public class Stock
{
public string Id { get; set; }
public StockStatus Status { get; set; }
}
I'm trying to sync the list with this algorithm:
Update or insert all stocks downloaded now as "StillActive".
Any stocks with the status of "Active" from last time means they weren't in the update and need to be "Deleted".
All stocks still "StillActive" become the new "Active" stocks.
Here is the implementation:
List<Stock> stocks = DownloadStocks();
using (var session = RavenContext.Store.OpenSession())
{
foreach (Stock stock in stocks)
{
stock.Status = StockStatus.StillActive;
session.Store(stock);
}
session.SaveChanges();
session.PatchUpdateCutoffNow("Stocks/ByStatus", "Status:Active", "Status", StockStatus.Deleted);
session.PatchUpdateCutoffNow("Stocks/ByStatus", "Status:StillActive", "Status", StockStatus.Active);
}
PatchUpdateCutoffNow is an extension method that does an UpdateByIndex with a Cutoff of now:
public static void PatchUpdateCutoffNow(this IDocumentSession session, string indexName, string query, string name, object val)
{
session.Advanced.DatabaseCommands.UpdateByIndex(indexName,
new IndexQuery() { Query = query, Cutoff = DateTime.Now },
new[]
{
new PatchRequest
{
Type = PatchCommandType.Set,
Name = name,
Value = val.ToString()
}
});
}
I end up with a lot of stocks Deleted that shouldn't be. My guess is the SaveChanges is asynchronous and doesn't finish by the time the PatchUpdateCutoffNow starts so I end up with some amount of Stocks with a status of "Deleted" when they should be "Active". I guess the IndexQuery cutoff doesn't apply since the SaveChanges isn't directly against the "Stocks/ByStatus" index.
Is there a way to make SaveChanges synchronous or some other way to do this that fits more with the NoSQL/RavenDB way of thinking?
The documents are stored right away, but the patch commands work on an index which has not updated yet. Maybe this helps, inserted between SaveChanges() and the patching:
using (var s = RavenContext.Store.OpenSession()) {
s
.Query<Stock>("Stocks/ByStatus")
.Customize(c => c.WaitForNonStaleResultsAsOfNow())
.Take(0)
.ToArray();
}
By the way, you don't need a session for DatabaseCommands, you can call them directly on the store.

Categories