Winforms, EF Core, dropdown combobox bound to BindingSource. Is there a descent way of persisting newly added items? BindingList? - winforms

I am trying to build a Patient's edit form that will get Patient data from a database using EF Core and the DbContext derived AppDbContext.
On the same form there will be a dropdown combobox that will be displaying all the available insurances (fetched from the database).
What I want to achieve is the ability, the user to be able to select an existing insurance (which is obvious and easily achieved) or to add a new one by typing it into the combobox and this new entry should be selected as the patient's insurance from now on until the SaveChanges takes place and when the same patient is reopened for editing.
I use two BindingSources one for the patient itself (bsPatient) and one for the insurances list (bsInsurances).
I have the following two models (1:many relationship)
public class Insurance
{
public int Id { get; set; }
public string Name { get; set; } = String.Empty;
public virtual ObservableCollectionListSource<Person> Persons { get; } = new();
}
public class Person
{
public int Id { get; set; }
public string LastName { get; set; } = String.Empty;
public string FirstName { get; set; } = String.Empty;
public DateTime DOB { get; set; }
public int InsuranceId { get; set; }
public Insurance Insurance { get; set; } = null;
}
And this is the DbContext:
public class AppDbContext : DbContext
{
private const string DatabaseFileName = "MyPatientsDB.sqlite3";
public DbSet<Person> Persons { get; set; }
public DbSet<Insurance> Insurances { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder
.UseSqlite(#$"Data Source={DatabaseFileName}");
On the form there will be
public partial class PatientForm : Form
{
private AppDbContext _db = new();
private int _patientId = 0;
ObservableCollection<Insurance> _insurancesList = new();
public PatientForm(int patientId)
{
InitializeComponent();
_patientId = patientId;
}
protected async override void OnLoad(EventArgs e)
{
base.OnLoad(e);
await _db.Insurances.LoadAsync();
_insurancesList = new ObservableCollection<Insurance>(_db.Insurances.Local);
bsInsurances.DataSource = _insurancesList;
bsPatient.DataSource = await _db.Persons.FirstOrDefaultAsync(p => p.Id == _patientId);
}
protected async override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
bsInsurances.EndEdit();
if (cbInsurances.FindStringExact(cbInsurances.Text) == -1)
{
var newInsurance = new Insurance { Id = 0, Name = cbInsurances.Text };
_db.Insurances.Local.Add(newInsurance);
}
bsPatient.EndEdit();
await _db.SaveChangesAsync();
_db?.Dispose();
}
}
So far, I am able to save correctly the Insurance selection of the combobox when an already existing item is selected. The problem arises when the user inserts a new insurance entry into the combo textbox. This new entry can not be saved to the db and be displayed the next time the same patient is opened for editing.
I would be grateful if someone could point me towards which direction to follow to achieve this. I mean, while editing a patient's data how to be able to insert a new entry into the insurances combo and this new entry to be persisted into the db and be displayed and selected the next time the patient is opened for editing.

I think I've found a solution. I don't know if it is the best one but it seems to be working at least into my project. I am just referring it in case someone else has the same query.
Please if anyone has a better solution I would be grateful for his/her help.
protected async override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
bsInsurances.EndEdit();
if (cbInsurances.FindStringExact(cbInsurances.Text) == -1)
{
if (bsPatient != null && bsPatient.DataSource != null)
{
(bsPatient.DataSource as Person).Insurance = new Insurance() { Name = cbInsurances.Text as string };
}
}
bsPatient.EndEdit();
await _db.SaveChangesAsync();
_db?.Dispose();
}

Related

Bindingsource on combobox

I create the small winform to do test bing
Existing class as below
public class Book
{
public int Id { get; set; }
public string BookName { get; set; }
public int? CatalogID { get; set; }
}
public class BookCatalog
{
public int Id { get; set; }
public string CataLogName { get; set; }
}
and I create the form with two control, Textbox and Combobox
which initial as below
private Book BookRecord;
private List<BookCatalog> bookCatalogs;
private BindingSource BindingSource = new BindingSource();
public frmBook()
{
InitializeComponent();
// I have initial one bookrecord and multi bookCatalogs here
cbBookCatagories.DisplayMember = "CataLogName";
cbBookCatagories.ValueMember = "Id";
cbBookCatagories.DataSource = bookCatalogs;
BindingSource.DataSource = BookRecord;
txtBoxBookName.DataBindings.Add("Text", BindingSource, "BookName");
cbBookCatagories.DataBindings.Add("SelectedValue", BindingSource, "CatalogID");
}
The first run is fine, but when I want to clear BookRecord as below code
BookRecord.Id = 0;
BookRecord.BookName = null;
BookRecord.AuthorName = null;
BookRecord.CatalogID = null;
BindingSource.ResetBindings(false);
My combobox cannot change value and always point to zeroindex
can anyone guide me how to handle BindingSource() on comboxbox ?
Thank you

Entity framework 6 not updating foreign key relation

Hello I have a strange issue here. I have a Project model that has a foreign key to the Company model. The thing is that when I attach my Project property in order to update it, then it updates all other primitive fields, except for the Company.
The Project model looks like this:
public class Project
{
[Key]
public int ID { get; set; }
[Index(IsUnique = true)]
public string Name { get; set; }
public virtual Company Company { get; set; }
public bool ExportProjectName { get; set; }
public DateTime CreatedAt { get; set; }
}
Then what I do, is I have a ProjectsViewModel, that gets all Projects from the Database. I wrap each of them then in a ProjectViewModel which exposes some fields of the Project and also has a saving method. I think the ProjectsViewModel implementation may not be that important so I'll paste only the two lines that populate the ProjectViewModels:
var projects = dbcontext.Projects.Include(x => x.Company).ToList().Select(x => new ProjectViewModel(x));
this.ListOfProjects = new ObservableCollection<ProjectViewModel>(projects);
Then I have the ProjectViewModel. Take a look at the SaveProject method:
public class ProjectViewModel : INotifyPropertyChanged
{
private Project _project;
public Project Project
{
get { return _project; }
set
{
_project = value;
NotifyPropertyChanged("Project");
}
}
public int ID
{
get { return Project.ID; }
set
{
Project.ID = value;
NotifyPropertyChanged("ID");
}
}
public string Name
{
get { return Project.Name; }
set
{
Project.Name = value;
NotifyPropertyChanged("Name");
}
}
public Company Company
{
get { return Project.Company; }
set
{
Project.Company = value;
NotifyPropertyChanged("Company");
}
}
public ProjectViewModel(Project project)
{
this.Project = project;
}
public void SaveProject()
{
using (DbContext dbcontext = new DbContext())
{
// At this state this.Project and this.Company exist in the database
dbcontext.Companies.Attach(this.Company);
dbcontext.Projects.Attach(this.Project);
dbcontext.Entry(this.Project).State = EntityState.Modified;
dbcontext.SaveChanges();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
And that's it. I have the SaveProject bound to a command, which I just trigger.
And then in the logs I can only see such a query:
Opened connection at 08/12/2018 17:47:21 +01:00
Started transaction at 08/12/2018 17:47:21 +01:00
UPDATE "public"."Projects" SET "Name"=#p_0,"ExportProjectName"=#p_1,"CreatedAt"=#p_2 WHERE "ID" = #p_3
So all properties are there, except for the Company. If that's important - I'm using PostgreSQL with NpgSql. I saw many question on SO in regards to the related object not being updated itself, but I couldn't find any question where the relation would be only broken. Hope somebody can help!
EDIT:
BTW the code below would work, but I do not want to assign all properties by myself and would want to avoid getting the object from the DB one more time. And I want to know, why the relation is not updated in the first case, as it doesn't make sense for me.
dbcontext.Companies.Attach(this.Company);
var p = dbcontext.Projects.Single(x => x.ID == this.ID);
p.Name = this.Name;
p.Company = this.Company;
dbcontext.SaveChanges();

WPF Datagrid get data on CheckboxChecked-Event

I've got a datagrid with a checkbox, name and email.
On CheckboxChecked I want to copy the email into another list or string.
How do I get the specific value from the checked row?
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
object row = lbxCC1.SelectedItem;
int columnIndex = lbxCC1.Columns.Single(c => c.Header.Equals("eMail")).DisplayIndex;
String eMail = (lbxCC1.SelectedCells[columnIndex].Column.GetCellContent(row) as TextBlock).Text;
MessageBox.Show(eMail);
}
Edit (09.09.2016):
Maybe I should show you a bit more code.
public class Person
{
public string Nachname { get; set; }
public string Vorname { get; set; }
public string eMail { get; set; }
public string Abteilung { get; set; }
}
public static class PersonService
{
public static List<Person> ReadFile(string filepath)
{
var lines = File.ReadAllLines(filepath);
var data = from l in lines.Skip(1)
let split = l.Split(';')
select new Person
{
Nachname = split[1],
Vorname = split[2],
eMail = split[31],
Abteilung = split[4],
};
return data.ToList();
}
}
I call it with:
lbxCC1.DataContext = PersonService.ReadFile(#"C:\Test.csv");
As I'm building the columns from code behind, I guess I have to bind them aswell am I right?
Sorry for this, but I'm new to datagrids :-)
I think this might help you:
Dim row As Data.DataRowView = DirectCast([yourDataGrid].SelectedItems(rowIndex), Data.DataRowView)
Then in your CheckBox_Checked Event:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
MessageBox.Show(row("email"); 'Assuming the column name is email
}
This is IF your values are data-bound to the DataGrid.

Update model in database using entity framework gives conflict with foreign key

I know this has been asked millions of times and I've had it myself hundreds of times, but for some reason I can't fix this one.
I get the well known error:
The UPDATE statement conflicted with the FOREIGN KEY constraint ...
All my tables in my database are cascaded when an insert or delete is done.
Now on to the error:
I want to update an admins table (administrator accounts) that is linked to a cultures table (for languages).
Everything is filled in correctly. and thus we get to the following code:
[HttpPost]
public ActionResult Edit(Admins admins)
{
if (!ModelState.IsValid)
{
return View(admins);
}
admins.cultures_id = admins.Cultures.id;
_unitOfWork.AdminsRepository.Update(admins);
_unitOfWork.Save();
return RedirectToAction("Index", "Overview", new { area = "Admin" });
}
I first set the cultures id of my admin object/entity equal to that of the id in the cultures table that is linked:
admins.cultures_id = admins.Cultures.id;
I then fill update the table:
_unitOfWork.AdminsRepository.Update(admins);
The method update holds this code:
public virtual void Update(TEntity entityToUpdate)
{
DbSet.Attach(entityToUpdate);
ArtWebShopEntity.Entry(entityToUpdate).State = EntityState.Modified;
}
So far so good, but then, when I actually want to save the admin:
_unitOfWork.Save();
That save method holds this code:
public void Save() {
try
{
_artWebshopEntity.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:", validationErrors.Entry.Entity.GetType().Name, validationErrors.Entry.State);
foreach (var validationError in validationErrors.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"", validationError.PropertyName, validationError.ErrorMessage);
}
}
throw; // Will do something here later on...
}
}
And at the SaveCHanges method I get the error. I know what it means but I can't seem to fix it. I've tried all the things I know that could cause it.
Edit
I only want to update the admin values, so I don't want to update the culture values.
This is the query:
update [dbo].[Admins]
set [login] = 'Herve' /* #0 */,
[password] = null,
[salt] = null,
[email] = 'xxxxx.xxx#glevin.be' /* #1 */,
[permissions] = 'administrator' /* #2 */,
[attempts] = 4 /* #3 */,
[locked] = 0 /* #4 */,
[cultures_id] = 0 /* #5 */
where ([id] = 1 /* #6 */)
So, the cultures_id is the issue. I've now did the following:
var updateAdmin = new Admins
{
attempts = admins.attempts,
cultures_id = admins.cultures_id,
email = admins.email,
locked = admins.locked,
login = admins.login,
id = admins.id,
password = admins.password,
permissions = admins.permissions,
salt = admins.salt,
};
And that works, but the moment I add the Cultures object to the mix, it crashes and gives me the reference error. So it boils down to, how the frack do I update a table with a foreign key to another table to also needs to be updated?
Edit II
My admin and cultures entity (database first), also image of database in sql management studio:
Admin class:
public partial class Admins
{
public int id { get; set; }
public string login { get; set; }
public string password { get; set; }
public string salt { get; set; }
public string email { get; set; }
public string permissions { get; set; }
public Nullable<int> attempts { get; set; }
public Nullable<bool> locked { get; set; }
public Nullable<int> cultures_id { get; set; }
public virtual Cultures Cultures { get; set; }
}
Cultures class:
public partial class Cultures
{
public Cultures()
{
this.Categories_local = new HashSet<Categories_local>();
this.Menu_items_local = new HashSet<Menu_items_local>();
this.Products_local = new HashSet<Products_local>();
this.Subcategories_local = new HashSet<Subcategories_local>();
this.Webpages_local = new HashSet<Webpages_local>();
this.Admins = new HashSet<Admins>();
}
public int id { get; set; }
public string name { get; set; }
public string display_name { get; set; }
public virtual ICollection<Categories_local> Categories_local { get; set; }
public virtual ICollection<Menu_items_local> Menu_items_local { get; set; }
public virtual ICollection<Products_local> Products_local { get; set; }
public virtual ICollection<Subcategories_local> Subcategories_local { get; set; }
public virtual ICollection<Webpages_local> Webpages_local { get; set; }
public virtual ICollection<Admins> Admins { get; set; }
}
I've gotten it to work!
The problem was that in the edit page the final field was the field that showed the name of the culture that corresponded with the id of the admin.
In other words I did the following:
#Html.EditorFor(model => model.Cultures.name)
But this wasn't the correct way.
In order to show the name of the culture but in the code pass the culture id, I used a #Html.DropDownListFor()-element.
The problem with this however was that my original model, Admins, didn't have a IEnumerable object that I could pass to the dropdownlist element in my view. I had to create a new model which I named CreateAdminModel, The new model looks like this:
public class CreateAdminModel
{
public CreateAdminModel() { }
public CreateAdminModel(IEnumerable<SelectListItem> cultures) { Cultures = cultures; }
public CreateAdminModel(Admins admin) { Admin = admin; }
public CreateAdminModel(IEnumerable<SelectListItem> cultures, Admins admin)
{
Cultures = cultures;
Admin = admin;
}
public IEnumerable<SelectListItem> Cultures { get; set; }
public Admins Admin { get; internal set; }
}
It has an Admin object created by the entity framework (database first).
With that new model I created the following method:
private CreateAdminModel CreateAdminWithcultureDetails(Admins admin = null)
{
var cultureItems = (_unitOfWork.CulturesRepository.Get()).ToArray();
var cultureList = new List<SelectListItem>();
for (int i = 0; i < cultureItems.Count(); i++) cultureList.Add(new SelectListItem { Text = cultureItems[i].name, Value = cultureItems[i].id.ToString() });
return admin != null ? new CreateAdminModel(cultureList, admin) : new CreateAdminModel(cultureList);
}
This fills the dropdown list with the cultures and depending on whether or not an admin object was passed also adds an admin object.
Now I can use this model in the view and correctly fill both the dropdown list and the admin if necessary.
I'm going to do the same for the other things that have to use CRUD.

MVC Persist Collection ViewModel (Update, Delete, Insert)

In order to create a more elegant solution I'm curios to know your suggestion about a solution to persist a collection.
I've a collection stored on DB.
This collection go to a webpage in a viewmodel.
When the go back from the webpage to the controller I need to persist the modified collection to the same DB.
The simple solution is to delete the stored collection and recreate all rows.
I need a more elegant solution to mix the collections and delete not present record, update similar records ad insert new rows.
this is my Models and ViewModels.
public class CustomerModel
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PreferredAirportModel> PreferedAirports { get; set; }
}
public class AirportModel
{
public virtual string Id { get; set; }
public virtual string AirportName { get; set; }
}
public class PreferredAirportModel
{
public virtual AirportModel Airport { get; set; }
public virtual int CheckInMinutes { get; set; }
}
// ViewModels
public class CustomerViewModel
{
[Required]
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PreferredAirporViewtModel> PreferedAirports { get; set; }
}
public class PreferredAirporViewtModel
{
[Required]
public virtual string AirportId { get; set; }
[Required]
public virtual int CheckInMinutes { get; set; }
}
And this is the controller with not elegant solution.
public class CustomerController
{
public ActionResult Save(string id, CustomerViewModel viewModel)
{
var session = SessionFactory.CurrentSession;
var customer = session.Query<CustomerModel>().SingleOrDefault(el => el.Id == id);
customer.Name = viewModel.Name;
// How can I Merge collections handling delete, update and inserts ?
var modifiedPreferedAirports = new List<PreferredAirportModel>();
var modifiedPreferedAirportsVm = new List<PreferredAirporViewtModel>();
// Update every common Airport
foreach (var airport in viewModel.PreferedAirports)
{
foreach (var custPa in customer.PreferedAirports)
{
if (custPa.Airport.Id == airport.AirportId)
{
modifiedPreferedAirports.Add(custPa);
modifiedPreferedAirportsVm.Add(airport);
custPa.CheckInMinutes = airport.CheckInMinutes;
}
}
}
// Remove common airports from ViewModel
modifiedPreferedAirportsVm.ForEach(el => viewModel.PreferedAirports.Remove(el));
// Remove deleted airports from model
var toDelete = customer.PreferedAirports.Except(modifiedPreferedAirports);
toDelete.ForEach(el => customer.PreferedAirports.Remove(el));
// Add new Airports
var toAdd = viewModel.PreferedAirports.Select(el => new PreferredAirportModel
{
Airport =
session.Query<AirportModel>().
SingleOrDefault(a => a.Id == el.AirportId),
CheckInMinutes = el.CheckInMinutes
});
toAdd.ForEach(el => customer.PreferedAirports.Add(el));
session.Save(customer);
return View();
}
}
My environment is ASP.NET MVC 4, nHibernate, Automapper, SQL Server.
Well, if "elegant" is just "don't clear and recreate all" (untested) :
var airports = customer.PreferedAirports;
var viewModelAirports = viewModel.PreferredAirports;
foreach (var airport in airports) {
//modify common airports
var viewModelAirport = viewModelAirports.FirstOrDefault(m => m.AirportId == airport.AirportId);
if (viewModelAirport != null) {
airport.X = viewModelAirport.X;
airport.Z = viewModelAirport.Z;
//remove commonAirports from List
viewModelAirports.Remove(viewModelAirport);
continue;
}
//delete airports not present in ViewModel
customer.PreferedAirports.Remove(airport);
}
//add new airports
foreach (var viewModelAirport in viewModelAirports) {
customer.PreferedAirports.Add(new PreferredAirportModel {
Airport = session.Query<AirportModel>().SingleOrDefault(a => a.Id == el.AirportId),
CheckInMinutes = el.CheckInMinutes
});
}
session.Save(customer);

Resources