0

I have some objects which are loaded from an SQLite database into a separate list. The user can select an item from that list via combobox and edit its data in a subform. I work with Binding so that the list and its item is immediately updated on every change. Furthermore, I have a LastModified field (DateTime) in order to see the time of the last change which is set via my item SaveToDB() method.

Now, I am wondering how to handle the database update as well as the display of updated bound values correctly and have 3 open questions:

  1. How am I able to store the old item values in order to compare those with the final edit (after leaving the edit control, like a TextBox) without reading the database entry again? As all bound variables are immediately changed with the edit I think I would need a copied item object when the object is loaded and saved inbetween but which is not bound itself. How can I create such a copy?

  2. What is the best event to fire the comparison and database update? I think it is Validated() but I am not sure. It definitely is not the Changed() event as here a database update would be triggered after every keystroke, even when the changes are undone again before leaving the edit control (for which I want to use the comparison in point 1 to get rid of unnecessary database updates).

  3. Why is my bound DateTime label not updated when I call the entity SaveToDB() method? Do you need to rebind every control after each code internal change of object properties? Seriously? I find it also a bit stupid to explicitely update the combobox.Text while the updated data is correctly displayed when I dropdown the combobox. Really strange behaviour.

Code:

public class Entity {
    public int ID { get; set; }
    public string DateTime { get; set; }
    public string Name { get; set; }

    ...

    public void SaveToDB() {
        DateTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        if (DB.Read(QueryRead()) == null) {
            DB.Write(QueryAdd());
        } else {
            DB.Write(QueryUpdate());
        }
    }
}

public class Entities : SortableBindingList<Entity> {
    ...
}

public class EntityStore {
    public Entities Entities;
    public BindingSource Source; // used for combobox.datasource in selector-form

    // singleton
    ...

    public EntityStore() {
        Entities = new Entities();
        Source = new BindingSource() { DataSource = Entities };
        ReadAllFromDB(); // fills Entities
    }

    ...

    public void Update(Entity entity) {
        entity.SaveToDB();
    }
}

public partial class FormSelector : Form {
    // FormEntity gets the selected entity via its parent form:
    FormEntity formEntity;
    ...
    cbxEntity.DataSource = EntityStore.Current.Source;
    formEntity.SetEntity(EntityStore.Current.Entities.FirstOrDefault(x => x.ID == ((Entity)((GridViewRowInfo)(cbxEntity.SelectedItem)).DataBoundItem).ID));
    ...
}

public partial class FormEntity : Form {
    Entity entity;

    ...

    public void SetEntity(Entity entity) {
        this.entity = entity;
        tbxID.DataBindings.Add(new Binding("Text", entity, "ID", false));
        lblDateTime.DataBindings.Add(new Binding("Text", entity, "DateTime", false));
        tbxName.DataBindings.Add(new Binding("Text", entity, "Name", false));
    }

    private void tbxName_Validated(object sender, EventArgs e) {
        // missing comparison logic
        // ...

        // I could also directly call entity.SaveToDB(); here but I would like
        // to keep the logic flow separated as follows:
        // DB <- Entity <- EntityStore <- Form(s)
        EntityStore.Current.Update(entity);
    }
}

Sidenote/Question: I also stumbled over the INotifyPropertyChanged event but I do not understand if its used in winforms and for what purpose.

To prevent a stack overflow exception by implementing INotifyPropertyChanged you additionally need to use private properties when getting and setting public properties.

tar
  • 156
  • 2
  • 13
  • Do you use EF DbContext and DbSet? or do you use classic ADO.NET classes like DataSet, TableAdapter,etc? Both of mentioned approaches gives you some good mechanisms for change tracking. But if you don't use those approaches, then you need to design something for yourself, based on the features that you get from BindingList, ObservableCollection, INotifyPropertyChanged. – Reza Aghaei Dec 28 '21 at 10:03
  • [`INotifyPropertyChanged`](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?WT.mc_id=DT-MVP-5003235&view=net-6.0) is not necessary for data binding, but it enables two-way data binding. In simple one-way data binding, when you change the bound property of a control, the value will be pushed into the bound property of your object, it doesn't need `INotifyPropertyChanges`. But without `INotifyPropertyChanged`, if you change the value of the bound property of your object using code, the new value won't be pushed to your control's bound property. – Reza Aghaei Dec 28 '21 at 10:14
  • It explains the situation of your datetime property and why it's not updated in UI. You can also read more [here](https://stackoverflow.com/a/33625054/3110834). – Reza Aghaei Dec 28 '21 at 10:24
  • The best event for saving to db... it may be when the user clicks on a Save button, or closes the edit modal, or maybe when the user leaves a control. So it really depends on the requirements. – Reza Aghaei Dec 28 '21 at 10:29
  • Hi Reza, I do not use EF nor ADO. I have just tried to implement the INotifyBlub but now I get a stack overflow exception when trying to create a mapped object within `return new Entity` in ` MapFromDB()`, see my edit. The debugger does not give me any hints. – tar Dec 28 '21 at 11:34
  • I solved the exception by adding private properties which are used within the GET/SET of the public properties (which feels redundant). The label is updated now but still the combobox.Text (which is in the parent form) is not but its DataSource is a BindingSource which has a DataSource that is a SortableBindingList which includes/inherits from iBinding (jesus...). Whelp. Also, I still need the copied object for comparison before triggering SaveToDB(). – tar Dec 28 '21 at 12:22
  • Or maybe you just need to implement an IsDirty property for the object which tracks the dirty state of the object, or listen to the `ListChanged` event of the `IBindingList` or the `BindingSource`, just track the inserted, updated or deleted entities and save changes. – Reza Aghaei Dec 28 '21 at 13:30
  • The question is too broad to have an answer; I also recommend you to learn a bit about EF and maybe using EF provider for SQLite, which at least prevents you from reinventing the wheel (mapping db to your objects and pushing objects back to db which is responsibility of an ORM like EF). – Reza Aghaei Dec 28 '21 at 13:33

0 Answers0