I am testing the workings of WPF with Entity Framework. I have a SS table called Vendors {VendorCode, VendorName, Phone}.
I am sticking with only EF and I am able to display and navigate the recordset on a WPF form with buttons first, next, last etc. I used the instructions on the MSDN site (Create a simple data application with WPF and Entity Framework 6)
My problem is the recordset is sorted only in the order it was entered into SS. I would like to sort it by VendorCode or by VendorName to make it easier on the user. I can't seem to make it sort the recordset or table data coming through EF.
Can you please help? Thank you!
Here is a snippet of my code:
public Vendor newVendor { get; set; }
VendorsEntities context = new VendorsEntities();
CollectionViewSource VendorViewSource;
public MainWindow()
{
InitializeComponent();
newVendor = new Vendor();
VendorViewSource = ((CollectionViewSource)
(FindResource("VendorViewSource")));
DataContext = this;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// this next line doesn't do it
context.Vendors.OrderBy(Vendor => Vendor.VendorCode);
context.Vendors.Load();
VendorViewSource.Source = context.Vendors.Local;
}
private void NextCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
VendorViewSource.View.MoveCurrentToNext();
}
You would need to set the result of OrderBy method in to some variable and then use that as OrderBy will return a new reference, or you can use the set the reference of context.Vendors to the reference returned by OrderBy() method.
Try doing it like:
var ordered = context.Vendors.OrderBy(Vendor => Vendor.VendorCode);
VendorViewSource.Source = ordered;
another way can be to order it after bringing the result back, but it is not a recommended approach, first approach should be preferred, but just giving another option which is also possible:
var vendors = context.Vendors.Load().OrderBy(Vendor => Vendor.VendorCode);
VendorViewSource.Source = vendors;
Hope it helps!
You are sorting context, not displayed items. Try:
VendorViewSource.Source = context.Vendors.OrderBy(Vendor => Vendor.VendorCode);
Related
I have bound a DataGridView to an SQL Server table in a .Net 5.0 WinForms project. Displaying the data works well.
I would like to update editions to the database as soon as I move to another row in the DataGridView. But I have not found a way to do this.
The solution presented here seems not to work with an OleDbDataAdapter. The Update method does not accept a DataRow. Examples in DOCS require a DataSet which I try to avoid. Other examples use a button to save changes.
The data gets loaded like this:
var dataAdapter = new OleDbDataAdapter(sqlQueryString, connString);
var dataTable = new DataTable();
dataAdapter.Fill(dataTable); // fill data table from SQL server
var bindingSource = new BindingSource();
bindingSource.PositionChanged += new System.EventHandler(bindingSource_PositionChanged);
bindingSource.DataSource = dataTable; // connect binding source to data table
dataGridView.DataSource = bindingSource; // connect DataGridView to binding source
For the update I finally have tried this:
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
DataRow dataRow = ((DataRowView)((BindingSource)sender).Current).Row;
if (dataRow.RowState == DataRowState.Modified) // this is successful
{
dataAdapter.Update(dataRow); // compile error
}
}
I get the compile error
Cannot convert from 'System.Data.DataRow' to 'System.Data.DataRow[]'.
Any hint is appreciated.
MVVM
In modern programming, there is the tendency to separate the model from the view. This separation makes it easier to change the way that your data is displayed without having to change your model. You can also change parts of the model without having to change the display. It is easier to reuse the model and to unit test it without having to start a forms program.
In WPF this separation between model and view is almost enforced. When using winforms you have to take care that you do not mix them more than needed.
To keep these two separated, adapter code is needed to glue your model to your view. This adapter code is quite often called the viewmodel. the abbreviation of these three is quite often called MVVM. Consider to familiarize yourself with the ideas of MVVM.
Use a BindingList in your DataSource
If you want to separate your model from your view, you need methods to fetch the data that must be displayed from the database, and data to update items.
I don't know what you will be displaying in your DataGridView, but let's assume it is a sequence of Products, something like this:
class Product
{
public int Id {get; set;}
public string ProductCode {get; set;}
public string Name {get; set;}
public string Description {get; set;}
public decimal Price {get; set;}
...
}
You will have methods to fetch the Products that must be displayed, and to Update one Product, or maybe several Products at a time:
IEnumerable<Product> FetchProductsToDisplay(...)
{
// TODO: fetch the products from the database.
}
void UpdateProduct(Product product) {...}
void UpdateProducts(IEnumerable<Product> products) {...}
Implementation is out of scope of this question. By the way, did you notice, that because I put fetching and updating data in separate procedures, I hid where the Products are saved? It can be in an SQL server, but if you want it could also be a CSV or XML file, or even a dictionary, which could be handy for unit tests.
Besides: you can unit tests these methods without using your forms.
Using the visual studio designer you have added the columns and defined which column should show which Product property. You could also have done this in the constructor using property DataGridViewColumn.DataPropertyName
public MyForm()
{
InitializeComponents();
this.columnProductCode.DataPropertyName = nameof(Product.ProductCode);
this.columnName.DataPropertyName = nameof(Product.Name);
...
}
You don't need to set the DataPropertyName for properties that you won't show anyway.
Now to display the products, it is enough to assign the Products to the DataSource of the DataGridView:
var productsToDisplay = this.FetchProductsToDisplay(...);
this.dataGridView1.DataSource = productsToDisplay.ToList();
This will display the products. However, changes that the operator makes: Add / Remove / Edit rows are not updated. If you need this functionality, then the Products need to put in an object that implements IBindingList, like (surprise!) BindingList<Product>:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
To Initialize the DataGridView:
private void DisplayProducts()
{
this.DisplayedProducts = new BindingList<Product>(this.FetchProductsToDisplay().ToList());
}
Now whenever the operator makes any change to the DataGridView: Add / Remove rows, or change the Displayed values in a row, these changes are reflected in DisplayedProducts.
If for instance the operator clicks Apply Now to indicate he has finished editing the products:
private void OnButtonApplyNow_Clicked(object sender, ...)
{
var products = this.DisplayedProducts;
// find out which Products are Added / Removed / Changed
this.ProcessEditedProducts(products);
}
Of course you can Add / Remove / Change displayed products programmatically:
void AddProductsToDisplay()
{
Product product = this.DisplayedProducts.AddNew();
this.FillNewProduct(product);
}
Back to your question
Ask yourself: Is it wise to update the database as soon as the position is changed?
If the operator starts typing, then remembers he can copy-paste items, he will stop typing, go to other controls to copy, and then continue editing the cell by pasting. Maybe he goes to other rows to look at information to decide what to put in the cell.
Another scenario: the Descriptions of Product A and Product B need to be exchanged. Think of the operator actions needed for this. When would it be wise to update the database? When are you certain that the operator is content with the new data?
Hence it is not wise to update the database as soon as a row is edited. The operator should explicitly indicate he has finished editing.
private void OnButtonOk_Clicked(object sender, ...)
{
var products = this.DisplayedProducts;
// find out which Products are Added / Removed / Changed
this.ProcessEditedProducts(products);
}
Further improvements
Once you've separated your data (model) from the way this data is displayed (view), using the DataSource, it is quite easy to access the Product that is displayed in the current row or in the selected rows:
Product CurrentProduct => (Product) this.dataGridView1.CurrentRow?.DataBoundItem;
IEnumerable<Product> SelectedProducts = this.dataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();
you can use foreach loop.
private void AddInfo()
{
// flag so we know if there was one dupe
bool updated = false;
// go through every row
foreach (DataGridViewRow row in dgv_Purchase.Rows)
{
// check if there already is a row with the same id
if (row.Cells["Pro_ID"].ToString() == txt_ProID.Text)
{
// update your row
row.Cells["Purchase_Qty"] = txt_Qty.Text;
updated = true;
break; // no need to go any further
}
}
// if not found, so it's a new one
if (!updated)
{
int index = dgv_Purchase.Rows.Add();
dgv_Purchase.Rows[index].Cells["Purchase_Qty"].Value = txt_Qty.Text;
}
}
Finally I've found the 2 missing lines:
private SqlCommandBuilder commandBuilder; // on UserControl level
commandBuilder = new SqlCommandBuilder(dataAdapter); // when loading data
A book has helped me: Michael Schmalz, C# Database Basics, O'Reilly
It is strange that the DOCS reference of SqlDataAdapter doesn't mention the SqlCommandBuilder.
Thanks to everybody who has spent precious time for a New contributor.
I'd like to apologise in advance in case my question was answered before (also the problem is probably pretty basic too). I'm pretty new to programming and there's a good chance I fail to google for answers since I don't even have a grasp of what the problem really is.
I'm trying to bind datagridview with data from linq query to entities (using Entity Framework).
I'm using this code to achieve that:
DatabaseModelContainer context = new DatabaseModelContainer();
private void FormDocumentUpdateWT_Load(object sender, EventArgs e)
{
context.Configuration.ProxyCreationEnabled = false;
context.DocumentDetailsSet.Load();
this.documentDetailsBindingSource.DataSource = (from x in context.DocumentDetailsSet.Local
where x.DocumentHeaderId == currentDocument.Id
select new
{
x.Inventory.Number,
x.Amount,
x.Containers.Name,
x.Separate,
x.Kanban
}).ToList();
dataGridViewItems.DataSource = documentDetailsBindingSource;
dataGridViewItems.Update();
dataGridViewItems.Refresh();
}
Unfortunately this is not giving me any results (datagridview doesn't show anything), eventhough the query does return data.
When I change the code like below, datagridview actually updates but (obviously) I do not get the specific properties of my desire displayed.
DatabaseModelContainer context = new DatabaseModelContainer();
private void FormDocumentUpdateWT_Load(object sender, EventArgs e)
{
context.Configuration.ProxyCreationEnabled = false;
context.DocumentDetailsSet.Load();
this.documentDetailsBindingSource.DataSource = (from x in context.DocumentDetailsSet.Local
where x.DocumentHeaderId == currentDocument.Id
select new {
x.Inventory, //this is changed
x.Amount,
x.Containers, // also changed
x.Separate,
x.Kanban
}).ToList();
dataGridViewItems.DataSource = documentDetailsBindingSource;
dataGridViewItems.Update();
dataGridViewItems.Refresh();
}
Can somebody explain to me how this works and how to work around this? Thanks in advance!
I've created a code first context with a DbSet property
I work with Windows form. If I bind as follow:
_context.Schedules.Load();
scheduleBindingSource.DataSource = _context.Schedules.Local.ToBindingList();
All works great and when I save as follow:
this.Validate();
scheduleBindingSource.EndEdit();
_context.SaveChanges();
The data persists; But when I bind the data like this:
var res = _context.Schedules.Where(k => k.EmployeeName.Equals(employeeComboBox.Text)).ToList();
scheduleBindingSource.DataSource = res;
When I save data doesn't persis!
I'm thinking that the ToList() method is not good, but I can't find alternative to get a BindingList connected to the Local set of data inside the context.
Thanks,
Andrea
You can try this:
_context.Schedules.Where(k => k.EmployeeName.Equals(employeeComboBox.Text)).Load();
scheduleBindingSource.DataSource = _context.Schedules.Local.ToBindingList();
That should only bring the schedules that meet the condition. When you call the Load method after the Where method, it is going to bring to memory only the records that meet the condition. Later, when you call the Local property,it will give you an ObservableCollection<Schedule> that contains all the objects that are currently tracked by the DbContext which thy are going to be the elements you loaded before. At the end, when you call the ToBindingList extension method, it will returns an BindingList<Schedule> that stays in sync with the given ObservableCollection<Schedules>.
The reason that caused the non-persistance of the data is caused by DataGridView (or the BindingSource), that don't add to context the new istance of the just added row.
So I've disabled the AllowUserToAddRow property (now I'm using BindingNavigator Add Button)
And implemented these two events as follow:
private void scheduleBindingSource_AddingNew(object sender, AddingNewEventArgs e)
{
_scheduleAdding = true;
}
private void scheduleBindingSource_CurrentChanged(object sender, EventArgs e)
{
if (_scheduleAdding)
{
Schedule s = (Schedule)scheduleBindingSource.Current;
s.EmployeeName = employeeComboBox.Text;
s.From = new DateTime(dateTimePicker1.Value.Year, dateTimePicker1.Value.Month, 1);
_context.Schedules.Add(s);
_scheduleAdding = false;
}
}
I have a texbox control in my wpf application where I will like to get a auto complete listbox as the user types in. In other words I have something like google's search box:
I have managed to do that in two ways and I will like to know which one is more efficient.
First way:
As user types into the textbox every time the texbox changes I update the listbox by quering the database. therefore I have something like:
void textBox1_KeyUp(object sender, KeyEventArgs e)
{
// new text
var content = ((TextBox)sender).Text;
// I am selecting the posible items using ado.net
var posibleItems= PdvEntities.Entities.TableFoos.Where(TableFoo=> TableFoo.Description.Contains(content)).Select(c=>c);
listbox1.ItemsSource = posibleItems;
}
note that with this approach I will be querying the database every time a keyup event fires on that textbox.
Second way:
Instead of querying the database everytime the keyup event fires, I do the following:
// select all items and store that as a global variable
IQueryable allItems = PdvEntities.Entities.TableFoos.Select(a => a);
void textBox1_KeyUp(object sender, KeyEventArgs e)
{
// new text
var content = ((TextBox)sender).Text;
// I don't have the code but I will then filter variable
// allItems based if their description contains 'content'
// pseudo code
newFileter <- filter of allItems that contain content
listbox1.ItemsSource = newFileter;
}
not that in this case I query the database just once and everytime I need to add items to the listbox I will query a IQueryable variable instead of the database. I am afraid that if the database is to big this technique will consume a lot of memory.
Also I forgot to mention that the database may not be local. Right now I am connecting to the database locally but this application may run with a remote database connection. Which approach is more efficient?
There won't be any significant difference between the two versions. That's because the following code doesn't do what you think it does:
// select all items and store that as a global variable
IQueryable allItems = PdvEntities.Entities.TableFoos.Select(a => a);
It doesn't “store” all items in the field, it doesn't even go to the database at all. It's just a query that can retrieve all the items if you iterate it.
On the other hand, if you did something like
Foo[] allItems = PdvEntities.Entities.TableFoos.ToArray();
that would actually retrieve all items into memory. But there's no way of knowing which one will be more efficient, if we don't know everything about your database and your execution environment.
If your PdvEntities class is an EntityFramework Context then the following is a Linq-to-Entities query which will generate T-SQL against your database and get only your filtered items.
var posibleItems= PdvEntities.Entities.TableFoos
.Where(TableFoo=> TableFoo.Description.Contains(content)).Select(c=>c);
Not sure I understand your other solution. As #svik mentions You could use ToArray() and ToList() to get all you items in memory, but this isn't going to perfomant at all.
It looks like you need to throttle calls to your database, so as the use types, every n seconds you send a query with the filter.
Have a look at system.reactive. It will allow you throttle your keyup event in a nice way.
I wrote an article on this here:
http://www.gideondsouza.com/blog/implementing-simple-instant-search-with-rx-reactive-extensions (Which is about just throttling a search)
And then another one which talks about linq-to-entities to throttle a database search:
http://www.gideondsouza.com/blog/abstracting-reactive-extensions-for-sql-server-compact-and-implementing-an-instant-search
Based on the stuff I've written in my articles you could do something like this:
You'll need a little helper
public class ObservableHelper<T>
where T : class //or EntityObject
{
public ObservableHelper()
{
_dat = new PdvEntities();
}
PdvEntities _dat;
public IObservable<IList<T>> GetAllAsObservables
(Func<PdvEntities, IQueryable<T>> funcquery)
{
var getall = Observable.ToAsync<PdvEntities, IQueryable<T>>(funcquery);
return getall(_dat).Select(x => x.ToList());
}
}
Then in your form:
public Form1()
{
InitializeComponent();
//your playing with IQueryable<TableFoos>
_repo = new ObservableHelper<TableFoos>()
Observable.FromEventPattern(h => textBox1.KeyUp += h,
h => textBox1.KeyUp -= h)//tell Rx about our event
.Throttle(TimeSpan.FromMilliseconds(500), cs)///throttle
.ObserveOn(Scheduler.Dispatcher);//so we have no cross threading issues
.Do(a => SearchList(textBox1.Text))//do this method
.Subscribe();
}
IObservableHelper<TableFoos, PdvEntities> _repo;
void SearchList(string query)
{//AS MANY keystrokes are there, this function will be called only
// once every 500 milliseconds..
listBox1.Items.Clear();
listBox1.BeginUpdate();
var getfn = _repo.GetAllAsObservables
(d => d.TableFoos.Where(c => c.TableFoos.Contains(query)));
getfn.ObserveOn(this).Subscribe(resultList => //is an IList<TableFoos>
{
foreach (var item in resultList)
{
listBox1.Items.Add(...
}
listBox1.EndUpdate();
});
}
I'm trying to display the content of a table in a combobox.
I'm using the MVVM pattern and in my viewmodel class if I write this it works:
private IEnumerable<EventType> _eventTypes;
public ManageProfileModel()
{
_referenceData = new ReferenceDataContext();
_referenceData.Load(_referenceData.GetEventTypesQuery(), false);
_eventTypes = _referenceData.EventTypes;
}
Like this the combobox is displaying the data.
However, I want the _eventTypes to be a List:
private List<EventType> _eventTypes;
But if I write this:
public ManageProfileModel()
{
_referenceData = new ReferenceDataContext();
_referenceData.Load(_referenceData.GetEventTypesQuery(), false);
_eventTypes = _referenceData.EventTypes.ToList();
}
then the combobox is empty. What is wrong with that?
I want to use a List, because I want to be able to add and remove data in the list.
Best regards.
If I remember correctly, you can not convert IEnumerable to IList directly. It is little tricky. I would use of the options from the following link. I have it in bookmark since I ran into the same problem.
http://devlicio.us/blogs/derik_whittaker/archive/2008/03/28/simple-way-to-convert-ienumerable-lt-entity-gt-to-list-lt-ientity-gt.aspx
or look at this link
http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/af225aa0-1cf4-40dd-ac3e-e7a19edaef00
DomainContext.Load is asynchronous, so in your second example you're creating a list that's most likely empty because the EntitySet hasn't finished loading yet. Use the code posted by StackOverflowException to defer creating the list until the EntitySet has been populated and it should work.
just a shot straight from the head...
did you try to add something like propertychanged event for your list?
so it could be that the data came async and the property was not informed about the change...
like I said ...
private List<EventType> _eventTypes;
public List<EventType> EventTypes
{
get { return _eventTypes; }
set
{
_eventTypes = value;
RaisePropertyChanged("EventTypes");
}
}
and take also a look at ObservableCollections...
Like I said just a shot...
Hope this helps
I don't have much MVVM exposure but with silverlight + RIA, I usually do something like this.
private List<EventType> _eventTypes;
public ManageProfileModel()
{
_referenceData = new ReferenceDataContext();
var op = _referenceData.Load(_referenceData.GetEventTypesQuery(), false);
op.Completed += op_Completed;
}
void po_Completed(object sender, EventArgs e)
{
var op = ( InvokeOperation<IEnumerable<EventType>>)sender;
_eventTypes = op.Value.ToList();
}