Showing information form from two tables - c#

I'm writing a course selection program using WPF and Entity Framework 6. In my database I have three tables one for students who wish to select a course, one for the information about the courses (name of the course, start time, etc) and one for the information about the instructors. I have a section in my program that lists all the courses with the corresponding data about each of them but because the instructors exist in a different table I am not able to show them in my ListView in other word I have problem in joining data of two table and show them in a single ListView:
and this is my code for showing the data of a course in a ListView which so far I'm unable to show the instructor due to the problem explained above
private void button_Click(object sender, RoutedEventArgs e)
{
using (var context = new Model1Container())
{
var course_List = new List<course>();
foreach (var course in context.courses)
{
course_List.Add(course);
}
listView.ItemsSource = course_List;
}
}

You have a one to many relationship between Course and Instructor, so, en Course entity you have a navigation property called instructor that you can use to show the related instructor.
Now my first suggestion is you don't need to iterate through all the courses to create a list, you can do this:
using (var context = new Model1Container())
{
var course_List = context.courses.Include(c=>c.instructor).ToList();
listView.ItemsSource = course_List;
}
The Include extension method allows you include in your query the related object that you want to be included in your query result.
Now, to see the Course info and the related Instructor you could define a custom ItemTemplate. As I said before, instructor is a Course property, so, you can do something like this.
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
...
<TextBlock Text="Instructor: " />
<TextBlock Text="{Binding instructor.name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>

I think it is btter to have InsctruorId in course table, and use below code for joining tabes
private void button_Click(object sender, RoutedEventArgs e)
{
using (var context = new Model1Container())
{
var course_List = new List<course>();
foreach (var course in GetAllCources)
{
course_List.Add(course);
}
listView.ItemsSource = course_List;
}
}
private List<course> GetAllCources()
{
return (from c in context.cources
join i in context.instructors on c.instructorId equals c.Id
select new
{
Cources = c,
Instructors = i,
}).AsEnumerable().Select(c => new Cources
{
.
.
All required fields
.
.
.
} : null).AsQueryable();
}
You can use Include if do not want to select properties.

Related

C# - SQlite + Entity - Show items on a ListView

Using Visual Studio 2015 and learning ORM with SQlite + Entity (WPF application)
My database with fields: ID and ShipType
I can populate a listView with the ShypType field:
private void button_Click(object sender, RoutedEventArgs e)
{
List<string> shipList = new List<string>();
using (var db = new ImperialFleetDBEntities())
{
shipList = (from g in db.ShipsType select g.ShipType).ToList();
db.Dispose();
}
listView.Items.Clear();
foreach (string str in shipList)
{
listView.Items.Add(str);
}
}
But I can't figure how to also diplay the ID field (g.ID on this example)
In this link C# ListView Display the solution is using ListViewSubItems
But If I type listView. I don't have any SubItems option
The solution is 5 years old, maybe that's de problem??
The simplest way would probably be:
private void button_Click(object sender, RoutedEventArgs e)
{
using (var db = new ImperialFleetDBEntities())
{
listView.Items.Clear();
foreach(var item in (from g in db.ShipsType select new { ID = g.ID, ShipType = g.ShipType }))
{
listView.Items.Add(item)
}
}
}
This should populate your listView in correct way and make sure these new objects won't be tracked by EF.
If you'd like, you could create full class, like ViewModel for listView containing fields you need and then select to them like:
foreach(var item in (from g in db.ShipsType select new NewShipType { ID = g.ID, ShipType = g.ShipType }))
{
listView.Items.Add(item)
}
or using constructor (if you make one).
With seprate ViewModel you can of course use in-between list object and clear and add them to listView.Items, which is impossible with anonymous object.
By the way, you don't have, and maybe you shouldn't write db.Dispose() inside using clause, as using is exactly meant to make sure the object inside is disposed - you cannot use using on object that is not implementing IDisposable interface.

Show data from related table in a data grid upon selecting a data grid row in WPF

I have two tables, Equipment and Components. Each Equipment consists of 1 or more components. Right now I have a data grid showing the Equipment, with related attributes from a View in my database.
What I want is to have a second data grid in the same window, which will show the component the selected Equipment in the data grid contains.
So far I know that I can get a selected row using the SelectedItem property:
Equipment eq= (Equipment )myDataGrid.SelectedItem;
But when should this code be run? I am using EF to map my DB entities to CLR objects, where I have included the component and its relation table as well.
When the user selects a row in the Equipment, of course I would need to refresh the component data grid with the new info, which I can do like this.
myGrid.ItemsSource = myDataSource;
How can I get started on this problem?
I am using a view, that includes data from 3 different tables in my Equipment data grid, so the table set to the data grid ItemsSource does not have a direct relation with the component table.
I fixed it by using getting the components and inserted them into the data grid when the SelectionChanged event on the Equipment data grid is called:
private void EquipDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
var row_list = GetDataGridRows(EquipDataGrid);
foreach (DataGridRow single_row in row_list)
{
if (single_row.IsSelected == true)
{
EquipmentView selectedEquipment = (EquipmentView)EquipDataGrid.SelectedItem;
using (wiki_nolek_dk_dbEntities db = new wiki_nolek_dk_dbEntities())
{
db.Configuration.LazyLoadingEnabled = true;
var equipmentRelation = db.EquipmentComponents.Where(c => c.EquipmentID == selectedEquipment.EquipmentId);
var componentsForEquipment = new List<Component>();
foreach (var row in equipmentRelation)
{
var component = db.Components.FirstOrDefault(c => c.ComponentId == row.ComponentID);
componentsForEquipment.Add(component);
}
CompDataGrid.ItemsSource = componentsForEquipment;
}
}
}
}
catch
{
MessageBox.Show("Det valgte udstyr eksisterer ikke.");
}
}
I have modified your own reply's code to remove useless foreach loop.I don't know how wiki_nolek_dk_dbEntities works exactly, but I'm also adding ToList() to any db query result to make sure the result is a List and not an IQueryable.
private void EquipDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
EquipmentView selectedEquipment = (EquipmentView)EquipDataGrid.SelectedItem;
using (wiki_nolek_dk_dbEntities db = new wiki_nolek_dk_dbEntities())
{
db.Configuration.LazyLoadingEnabled = true;
var equipmentRelationComponentIds =
db.EquipmentComponents
.Where(e => e.EquipmentID == selectedEquipment.EquipmentId)
.Select(e => e.ComponentId)
.ToList();
var componentsForEquipment =
db.Components
.Where(c => equipmentRelationComponentIds.Contains(c.ComponentId))
.ToList();
CompDataGrid.ItemsSource = componentsForEquipment;
}
}
catch
{
MessageBox.Show("Det valgte udstyr eksisterer ikke.");
}
}

How to properly bind data from Web API to ListBox in WPF?

I have an ASP.NET Web API which works all fine according to my tests. Now, I want to consume it in a simple WPF application. I just want to get the data from API, display it in the client app, and then to add or delete the data from the database just by using the client app.
My problem is that when I pass the data to a ListBox, I kind of lose information, specifically I lose the Id of the object, which I need later in order to delete it from the database. In my scenario, I have something like this in one of my pages:
<ListBox
Name="carCategoryListBox"
Grid.Row="2"
Grid.Column="1" />
And then, in the .cs file of the page, I do something like this:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
BindCarCategoryList();
}
private async void BindCarCategoryList()
{
var client = CarHttpClient.GetClient();
HttpResponseMessage response = await client.GetAsync("api/carcategories");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var carCategories = JsonConvert.DeserializeObject<List<DTO.CarCategory>>(content);
carCategoryListBox.ItemsSource = carCategories.Select(cc => cc.Category).ToList();
}
else
{
MessageBox.Show("Error code " + response.StatusCode + "\nMessage - " + response.ReasonPhrase);
return;
}
}
As you can see I just get the Category fields of my objects, and pass them to the ListBox to be displayed. The CarCategory class is pretty simple and can be seen below:
public class CarCategory
{
public int Id { get; set; }
public string Category { get; set; }
}
The problem is that at a later stage I need to get the actual Id of the object, and use it. But, since I pass only the Category field to the ListBox, this Id information is somehow lost in my case. If I do something like this:
int id = carCategoryListBox.SelectedIndex;
Then, I just get the Id of the item in the ListBox (which item it is in the list), I don't actually get the original Id of the object that is stored in the database. Any ideas how to pass the data to a ListBox in such a way that I still somehow preserve the original Id information?
ListBox.SelectedIndex is a position of selected item in ItemsSource. It can even be -1 if there is no selected item. And it is not the same value as Id
to get databound object make some changes:
1) use List<CarCategory> as ItemsSource:
carCategoryListBox.ItemsSource = carCategories;
2) set ListBox.DisplayMemberPath for it to display category name:
<ListBox DisplayMemberPath="Category"
Name="carCategoryListBox"
Grid.Row="2"
Grid.Column="1" />
3) use SelectedItem property to get selected item (can be null):
var category = (CarCategory)carCategoryListBox.SelectedItem;
You should bind the entire object not select just a field from it, and set the list box's DisplayMemberPath to 'Category'. Then your selected item will be the entire CarCategory, and you can get its ID.
You should also look at MVVM and try to get as close as possible to the concepts, they really help.
You have to change your BindCarCategoryList
carCategoryListBox.ItemsSource = carCategories.Select(cc => cc.Category).ToList();
to
carCategoryListBox.ItemsSource = carCategories;
and modify your xaml like this
<ComboBox Grid.Row="2" Grid.Column="1" Margin="4" Width="250" DisplayMemberPath="Category" DisplayMemberPath="Id" />
or if you dont use MVVM assign DisplayMemberPath and DisplayMemberPath directly in your .cs file

Property bound to DataGrid's SelectedItem does not change its child properties when CellEditEnding is fired

I have a DataGrid which looks like:
<DataGrid Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Purchases}" SelectionMode="Single" SelectionUnit="FullRow"
SelectedItem="{Binding SelectedPurchase, Source={x:Static ex:ServiceLocator.Instance}}"
AutoGenerateColumns="False" CanUserAddRows="False">
<e:Interaction.Triggers>
<e:EventTrigger EventName="CellEditEnding">
<e:InvokeCommandAction Command="{Binding DataContext.CellEditEndingCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"/>
</e:EventTrigger>
</e:Interaction.Triggers>
<DataGrid.Columns>
.......
........
<DataGrid.Columns>
</DataGrid>
Property SelectedPurchase looks like:
private Purchase _selectedPurchase;
public Purchase SelectedPurchase
{
get
{
return _selectedPurchase;
}
set
{
_selectedPurchase = value;
NotifyPropertyChanged("SelectedPurchase");
}
}
CellEditEndingCommand
public ICommand CellEditEndingCommand { get; set; }
private void CellEditEndingMethod(object obj)
{
XDocument xmlPurchases = XDocument.Load(DirectoryPaths.DataDirectory + "Purchases.xml");
var currentPurchaseInData = (from purchase in xmlPurchases.Element("Purchases").Elements("Purchase")
where Convert.ToInt32(purchase.Attribute("Id").Value) == ServiceLocator.Instance.SelectedPurchase.Id
select purchase).FirstOrDefault();
currentPurchaseInData.SetElementValue("CreditorId", ServiceLocator.Instance.SelectedPurchase.Creditor.Id);
currentPurchaseInData.SetElementValue("AnimalId", ServiceLocator.Instance.SelectedPurchase.Animal.Id);
currentPurchaseInData.SetElementValue("QuantityInLitre", ServiceLocator.Instance.SelectedPurchase.Litre);
currentPurchaseInData.SetElementValue("FAT", ServiceLocator.Instance.SelectedPurchase.FAT);
currentPurchaseInData.SetElementValue("RatePerLitre", ServiceLocator.Instance.SelectedPurchase.RatePerLitre);
xmlPurchases.Save(DirectoryPaths.DataDirectory + "Purchases.xml");
}
Now If I change any value in DataGridCell and then I hit Enter CellEditEndingCommand is fired and CellEditEndingMethod is fired. But If I keep a breakpoint inside CellEditEndingMethod and take a look at it, then I can see that Values of any property of SelectedPurchase does not change to new values.
Let me give an example to explain the above line more correctly:
When I keep a breakpoint on any line inside CellEditEndingMethod and take a look at Properties like Litre, FAT etc., these properties values does not change. I mean I expect the property to take new value but it holds old value. Also, In view I can see the new values but in XML file there are still old values.
Update:
Purchases = new ObservableCollection<Purchase>(
from purchase in XDocument.Load(DirectoryPaths.DataDirectory + "Purchases.xml")
.Element("Purchases").Elements("Purchase")
select new Purchase
{
Id = Convert.ToInt32(purchase.Attribute("Id").Value),
Creditor = (
from creditor in XDocument.Load(DirectoryPaths.DataDirectory + "Creditors.xml")
.Element("Creditors").Elements("Creditor")
where creditor.Attribute("Id").Value == purchase.Element("CreditorId").Value
select new Creditor
{
Id = Convert.ToInt32(creditor.Attribute("Id").Value),
NameInEnglish = creditor.Element("NameInEnglish").Value,
NameInGujarati = creditor.Element("NameInGujarati").Value,
Gender = (
from gender in XDocument.Load(DirectoryPaths.DataDirectory + #"Basic\Genders.xml")
.Element("Genders").Elements("Gender")
where gender.Attribute("Id").Value == creditor.Element("GenderId").Value
select new Gender
{
Id = Convert.ToInt32(gender.Attribute("Id").Value),
Type = gender.Element("Type").Value,
ImageData = gender.Element("ImageData").Value
}
).FirstOrDefault(),
IsRegisteredMember = creditor.Element("IsRegisteredMember").Value == "Yes" ? true : false,
Address = creditor.Element("Address").Value,
City = creditor.Element("City").Value,
ContactNo1 = creditor.Element("ContactNo1").Value,
ContactNo2 = creditor.Element("ContactNo2").Value
}
).FirstOrDefault(),
Animal = (
from animal in XDocument.Load(DirectoryPaths.DataDirectory + #"Basic\Animals.xml")
.Element("Animals").Elements("Animal")
where animal.Attribute("Id").Value == purchase.Element("AnimalId").Value
select new Animal
{
Id = Convert.ToInt32(animal.Attribute("Id").Value),
Type = animal.Element("Type").Value,
ImageData = animal.Element("ImageData").Value,
Colour = animal.Element("Colour").Value
}
).FirstOrDefault(),
Litre = Convert.ToDouble(purchase.Element("QuantityInLitre").Value),
FAT = Convert.ToDouble(purchase.Element("FAT").Value),
RatePerLitre = Convert.ToDouble(purchase.Element("RatePerLitre").Value)
}
);
The CellEditEnding Event is not meant to update the datarow but to validate the single cell and keep it in editing mode if the content is not valid. The real update is done when the whole row is committed. Try it by adding the code in the HandleMainDataGridCellEditEnding method in http://codefluff.blogspot.de/2010/05/commiting-bound-cell-changes.html to your CellEditEndingMethod. It is good explained there. You may replace the if (!isManualEditCommit) {} by if (isManualEditCommit) return;.
UPDATE
You can extend your Purchase class by interface IEditableObject. DataGrid will call the method EndEdit() of this interface after the data has been committed and so you can do the XML stuff there. So you don't need any further buttons because a cell goes in edit mode automatically and the commit is done when you leave the row.
I think the CollectionChanged solution does not work because if you edit a dataset all changes take place inside the single object (Purchase) and not in the collection. CollectionChanged will be called by adding or removing an object to the collection
2nd UPDATE
Another try by putting it all together:
I simplified your Purchase class for demonstration:
class Purchase
{
public string FieldA { get; set; }
public string FieldB { get; set; }
}
Create a derived class to keep the real Purchase class clean:
class EditablePurchase : Purchase, IEditableObject
{
public Action<Purchase> Edited { get; set; }
private int numEdits;
public void BeginEdit()
{
numEdits++;
}
public void CancelEdit()
{
numEdits--;
}
public void EndEdit()
{
if (--numEdits == 0)
{
if (Edited != null)
Edited(this);
}
}
}
This is explained in SO WPF DataGrid calls BeginEdit on an IEditableObject two times?
And create the Purchases collection:
ObservableCollection<EditablePurchase> Purchases = new ObservableCollection<EditablePurchase>()
{
new EditablePurchase {FieldA = "Field_A_1", FieldB = "Field_B_1", Edited = UpdateAction},
new EditablePurchase {FieldA = "Field_A_2", FieldB = "Field_B_2", Edited = UpdateAction}
};
Purchases.CollectionChanged += Purchases_CollectionChanged;
private void Purchases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (EditablePurchase item in e.NewItems)
item.Edited = UpdateAction;
}
void UpdateAction(Purchase purchase)
{
// Save XML
}
This provides that the calls to Edited are catched for all EditablePurchase elements from initialization and for newly created ones. Be sure to have the Edited property set in initializer
This is a disgrace for WPF. No DataGrid.CellEditEnded event? Ridiculous, and I didn't know about that so far. It's an interesting question.
As Fratyx mentioned, you can call
dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
in a code behind CellEditEnding method. While it works, I find it's quite ugly. Not only because of having code behind (could use a behavior to circumnavigate that), but your ViewModel CellEditEndingMethod will be called twice, once for no good reason because the edit is not yet committed.
I would probably opt to implement INotifyPropertyChanged in your Purchase class (I recommend using a base class so you can write properties on one line again) if you haven't already, and use the PropertyChanged event instead:
public MyViewModel()
{
Purchases = new ObservableCollection<Purchase>();
Purchases.CollectionChanged += Purchases_CollectionChanged;
}
private void Purchases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (Purchase item in e.NewItems)
item.PropertyChanged += Purchase_PropertyChanged;
if (e.OldItems != null)
foreach (Purchase item in e.OldItems)
item.PropertyChanged -= Purchase_PropertyChanged;
}
private void Purchase_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// save the xml...
}
You will not get any CollectionChanged event before DataGrid is changing the collection. And this does not before happen a dataset is committed.
If you press 'Enter' in a cell you change the value of this cell in a kind of copy of the real dataset. So it is possible to skip the changes by rollback. Only after finishing a row e.g. by changing to another row or direct commit your changed data will be written back to the original data. THEN the bindings will be updated and the collection is changed.
If you want to have an update cell by cell you have to force the commit as in the code I suggested.
But if you want to have a puristic MVVM solution without code behind you have to be content with the behavior DataGrid is intended for. And that is to update after row is finished.

Entity framework usage

I have a ListView which I filled in from SQL Server view called view_ListaKlientow with this query:
private void fillClientsList() {
using (var context = new EntityBazaCRM()) {
var listaKlientow = from d in context.view_ListaKlientow
select d;
objectListViewListaKlientow.SetObjects(listaKlientow.ToList());
objectListViewListaKlientow.AutoResizeColumns();
}
}
Then after user double clicks one row in ListView, I read ID from the view_ListaKlientow and use fillClientGui to fill necessary fields in gui (for a test only one field is included).
private void objectListViewListaKlientow_DoubleClick(object sender, EventArgs e) {
foreach (view_ListaKlientow user in objectListViewListaKlientow.SelectedObjects) {
int id = user.KlienciID;
fillClientGui(id);
TabPageActivate(tabControlMain, tabPageKlient);
}
}
private void fillClientGui(int klientId) {
using (var context = new EntityBazaCRM()) {
IQueryable<Klienci> klient = context.Kliencis.Include("Podmioty").Where(d => d.KlienciID == klientId);
foreach (var source in klient.ToList()) {
textNIP.Text = source.Podmioty.PodmiotNIP;
}
}
}
Now I'm wondering since I know exactly I'm querying for one ID i should be getting only particular client and not a list of clients so foreach from fillClientGui just to travers IQueryable<Klienci> seems like additional unnecessary code. Or this is how it should be done? I'm trying to learn Entity and some things aren't just clear to me yet :)
If you're sure that you've got only one instance to be returned form the database, you can use the extension function FirstOrDefault() in this case:
var source = context.Kliencis.Include("Podmioty").Where(d => d.KlienciID == klientId).FirstOrDefault();
textNIP.Text = source.Podmioty.PodmiotNIP;

Categories

Resources