How to implement INotifyPropertyChanged with LINQ-To-Entity objects - wpf

I am using a LINQ-to-Entity Model attached to my database. I can do normal binding using LINQ with no problems, example:
Dim db As New myEntityModel
dim myCustomers As New Customers
myCustomers=db.Customers.ToList
dim myItemSource = From c in myCustomers
Select c
myComboBox.ItemsSource = myItemSource
Easy! But my question is how do I implement INotifyPropertyChanged so that controls that I bind to are automatically updated whenever the data source changes?

Your code doesn't make a whole lot of sense :-)
First:
dim myItemSource = From c in myCustomers
Select c
This is not needed ad all, you can change the last line to this: myComboBox.ItemsSource = myCustomers. No need for myItemSource. This can be further simplified to myComboBox.ItemsSource = db.Customers.ToList. No need for myCustomers either.
Second:
You want your combobox to be updated when the customers change. Then why are you not binding directly to db.Customers but to a static list that will never change? db.Customers.ToList will create a snapshot of the contents of db.Customers. It will not be updated, when db.Customers is updated.
Conclusion:
Your code should look like this:
Dim db As New myEntityModel
myComboBox.ItemsSource = db.Customers

Related

Why is my vb code not updating the ms access database (only cached temporarly)

I am doing a small system using Ms. Access (the database has more than 10 tables ) connecting to visual studio. I made a public class for opening the connection to the database so I can use it in every form. Everything is working and I can get the data from the database But any inserting or deleting data in forms, the database in ms access not getting the update. I can see the new records in forms but nothing in the database.
Imports System.Data.OleDb
Public Class dbconnectClass1
'create db connection
Private DBcon As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0; Data Source=dental_clinic.accdb;")
'prepare db command
Private dbcmd As OleDbCommand
' db data
Public DBDA As OleDbDataAdapter
Public DBDT As DataTable
'query parameters
Public params As New List(Of OleDbParameter)
'query statics
Public recordcount As Integer
Public Exception As String
Public Sub ExecQuery(query As String)
'reset query status
recordcount = 0
Exception = ""
Try
'open connection
DBcon.Open()
'create db command
dbcmd = New OleDbCommand(query, DBcon)
'load params into dbcommand
params.ForEach(Sub(p) dbcmd.Parameters.Add(p))
'clear params list
params.Clear()
'excute command and fill dataset
DBDT = New DataTable
DBDA = New OleDbDataAdapter(dbcmd)
recordcount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
'close the database connection
If DBcon.State = ConnectionState.Open Then DBcon.Close()
End Sub
'include query and command parameters
Public Sub addparam(name As String, value As Object)
Dim newparam As New OleDbParameter(name, value)
params.Add(newparam)
End Sub
End Class
This my code inside the forms:
Public Class NewExpense
Private access As New dbconnectClass1
' a varuble having the appointment Id to connect between 2 forms
Private appointmentNo As Integer
Private Function NoError(Optional report As Boolean = False) As Boolean
If Not String.IsNullOrEmpty(access.Exception) Then
If report = True Then MsgBox(access.Exception)
Return False
Else
Return True
End If
End Function
Private Sub Savebuttum_Click(sender As Object, e As EventArgs) Handles
Savebuttum.Click
Dim oDate As DateTime = Convert.ToDateTime(DateTimePicker1.Value)
access.addparam("#expensenme", expensenmtXT.Text)
access.addparam("#expensedetail", ExpenseDetailTXT.Text)
access.addparam("#expenseamount", ExpenseAmountTXT.Text)
access.addparam("#expensedate", oDate)
access.addparam("#expensepaidTo", paidtoTXT.Text)
access.ExecQuery("INSERT INTO Expense (Expenses_name, expense_details,
expenses_amount, ExpenseDate_Paid, ExpensePaid_To) Values (#expensenme,
#expensedetail, #expenseamount, #expensedate, #expensepaidTo);")
'report on errors
If Not String.IsNullOrEmpty(access.Exception) Then
MsgBox(access.Exception) : Exit Sub
'success
access.DBDA.Update(access.DBDT)
MsgBox("Expense Has been Added Successfully")
End Sub
End Class
Hum, you have this:
params.ForEach(Sub(p) dbcmd.Parameters.Add(p))
Great, we add the parmaters - looks good to go!!!
then, next line CLEARS all the work above!!! (the parameters are removed!!!!)
'clear params list
params.Clear()
Next up? Many will build a connection object, then a reader, and then a adaptor. But you ONLY need a data adaptor if you going to update a data table. if you just going to execute a command, then you don't need the data table, and you don't need a adaptor FOR that table. Adaptor = ability to modify a existing datatable (or dataset).
You are MUCH better to use the command object.
Why?
Because the command object has a connection object (don't need a separate one)
Because the command object has a data reader for you (no need for a whole data adaptor to JUST fill a table. And remember, you don't need a whole data adaptor UNLESS you are going to send/update a data table back to the database.
And because the command object has the command text, then you don't even need a variable for that!!!
And because all objects are in "one object", then really all you need is something to handy get you the connection.
So, for your insert example, we really don't gain by having that object, do we?
Ok, so here is your insert code without using those extra objects:
so in the following, I declare ONE variable, - the sql command object.
And do the insert
And as FYI? Your save button is not a save - but a insert button - every time you hit it, you will insert a new row. Lets deal with that issue in a bit.
So, here is our code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using cmdSQL As New OleDbCommand("INSERT INTO Expense
(Expenses_name, expense_details,expenses_amount, ExpenseDate_Paid, ExpensePaid_To)
Values (#expensenme,#expensedetail, #expenseamount, #expensedate, #expensepaidTo)",
New OleDbConnection(My.Settings.TESTOLEDB))
With cmdSQL.Parameters
.Add("#expensenme", OleDbType.WChar).Value = expensenmtXT.Text
.Add("#expensedetail", OleDbType.WChar).Value = ExpenseDetailTXT.Text
.Add("#expenseamount", OleDbType.Currency).Value = ExpenseAmountTXT.Text
.Add("#expensedate", OleDbType.DBDate).Value = DateTimePicker1.Value
.Add("#expensepaidTo", OleDbType.WChar).Value = paidtoTXT.Text
End With
cmdSQL.Connection.Open()
cmdSQL.ExecuteNonQuery()
End Using
So things in above:
We have strong data typing and converstion.
Because of this, note how I did NOT have to create a separate date/time variable here.
Note the SAME for "money" or so called currency conversion - again strong data type by using parameters this way.
And is this a date only, or a date+ time value? If it is date, then
.Add("#expensedate", OleDbType.DBDate).Value = DateTimePicker1.Value
If it was/is a date + time, then this:
.Add("#expensedate", OleDbType.DBTimeStamp).Value = DateTimePicker1.Value
So, notice how all your object stuff REALLY did not help one bit, and in fact you did not really save code, and above actually had LESS variables defined to do the whole job.
Now, back to the insert issue/problem. (you save is doing a insert). But what about editing existing records?
So, I would suggest you work out the problem this way:
You create/get/have/assume a data row for the form.
The form takes the data row, fills the controls. You edit, and when you hit save, the data row is sent back to the datbase. So, once this works, then to add? Well, the add code will CREATE a new data row, save to database and THEN you send that new data row to the above eixsting form that can edit a data row, and can save a data row. So the form now is able to deal with both issues (adding vs editing existing). If the user dont' want the row, then you offer a delete button.
And REALLY nice is a data row means you don't deal with SQL, and don't deal with parmaters!!
So the code (desing pattern) I use is thus this:
dim da as oledbDataAdaptor
dim myTable as DataTable = MyRstEdit("SELECT * from tblHotels WHERE ID = " & lngID,da)
dim MyDataRow as DataRow = myTable.Rows(0)
' code to fill controls
txtHotelName.Text = MyDataRow("HotelName")
txtCity.Text = MyDataRow("City")
' etc. etc. etc.
Now to save? Well I put the values back into that DataRow like this:
MyDataRow("HotelName") = txtHotelName.Text
MyDataRow("City") = txtCity.Text
MyDateRow("BookingDate") = txtTimePick1.Value
da.Update(MyTable)
Notice how I don't have parameters, and even strong data type checking occurs for say the above Date/Time booking date column.
And the above is nice, since I don't have to deal with ANY parmaters to udpate a row of data.
The MyRstEdit routine looks like this and returns byREf a "da" (data adaptor).
Public Function MyrstEdit(strSQL As String, Optional strCon As String = "", Optional ByRef oReader As SqlDataAdapter = Nothing) As DataTable
' Myrstc.Rows(0)
' this also allows one to pass custom connection string - if not passed, then default
' same as MyRst, but allows one to "edit" the reocrdset, and add to reocrdset and then commit the update.
If strCon = "" Then
strCon = GetConstr()
End If
Dim mycon As New SqlConnection(strCon)
oReader = New SqlDataAdapter(strSQL, mycon)
Dim rstData As New DataTable
Dim cmdBuilder = New SqlCommandBuilder(oReader)
Try
oReader.Fill(rstData)
oReader.AcceptChangesDuringUpdate = True
Catch
End Try
Return rstData
End Function
So, now in vb.net, I actually find it is LESS code then even writing + using recordsets in MS-Access VBA code.
However, BEFORE you go down ANY of the above road?
Have you considered using the vb.net data binding features. Data-binding in vb.net means that you do NOT write ANY of the above code. it means that vb.net will do all of the dirty work, and write and setup ALL OF the code for you to edit data on a form. The end result is you don't write any code to update a table.
You do have to use + create a "data set". Once done, then you just drag controls onto the form, and you even get this. So you just drop in a dataset, table adaptor, Binding navagator, and you get this:
Note now the tool bar at the top (and you can place it on teh bottom if you wish). So you get this:
So that WHOLE form was created without having to write ONE line of code. And you can see we have navigation, edits and saves and even the ability to add. So, you can build up a editing form - and it thus becomes similar to say working in MS-Access and ZERO lines of code is required to build the above form.
However, if you ARE going to roll your own code? Then use a data row. That way you can shuffle data to/from the table, and NOT have to use parmaters and SQL update and insert statements - but ONLY have nice clean code in which you shove, or get values from that data row. .net will "write" all the update stuff for you.

Combining Formatting and Databinding from the database

I have a set of labels in a DataRepeater. The labels get their values from a table in a SQL database (Let say GetSQLResults gets the data from SQL database and returns a DataTable).
Dim salesDataTable As DataTable = GetSQLResults()
And for the binding
SalesLabel.DataBindings.Add("Text", salesDataTable , "Sales")
Where SalesLabel is a label in the form and "Sales" is the name of the column in the database.
What I want to do is to apply let say US money formatting to this Sales value that comes from the database. I don't know how to combine formatting information with the command that I wrote above for DataBinding. It is a Windows Form application and I am using VB .Net. Any help will be appreciated.
The Binding class has a FormatString property which you should be able to feed the standard or custom format string you desire.
I think perhaps something like:
Dim salesBinding As Binding = new Binding("Text", salesDataTable , "Sales")
salesBinding.FormatString = "C"
SalesLabel.DataBindings.Add(salesBinding)
Would do the trick.
I tried this based on nkvu answer and a post at http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/c860168f-46e5-4786-a68d-ac69c6a7a248/
Dim WithEvents salesBinding As Binding
salesBinding = New Binding("Text", salesDataTable , "Sales")
SalesLabel.DataBindings.Add(salesBinding)
Private Sub salesBinding_Format(ByVal sender As Object, ByVal e As System.Windows.Forms.ConvertEventArgs) Handles salesBinding .Format
If e.Value Is DBNull.Value Then
Else
e.Value = Format(e.Value, "c")
End If
End Sub
This did the trick for me.

LINQ datasource and BindingNavigator update deosn't work

I'm trying to set a LINQ query as the DataSource of a BindingNavigator control.
In the form's variables I added a data context:
`Private c
tx As New myDataManagerDataContext`
The following code allows me to display and navigate through the results:
Dim clubList = From c In ctx.clubs _
Select c
BindingNavigator1.BindingSource.DataSource = clubList
Each record is displayed nicely so far.
In the Winform, all textboxes are duly binded to the datasource but my problem is:
I added a Save ToolboxButton with: ctx.SubmitChanges()
But it doesn't process any update!
Question: Do I HAVE to write a complete Insert/ Update Linq procedure in the Save button?
Like:
Dim newClub as new DataContext.Club
newClub.Name = NameTextBox.Text
newClub.Address.... Etc.
Try to call EndEdit on binding source just before save operation to happen.

ADO.NET: Need help to understand the basics of 'Dataset'

As context, I am new to ADO.NET and have been using 'Programming ADO.NET 2.0' by David Sceppa to help build my knowledge.
I have been trying to understand the Dataset object but think I may have completely misunderstood the point and am looking for guidance.
As an example, I have built a really simple Form with a combobox with an aim of filling the combobox with the names of people in a database ("MyDatabase"). The following code works fine for me:
Private Sub frmEmployee_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim strConn, strSQL As String
strConn = "Data Source=.\SQLExpress;Initial Catalog=MyDatabase;Integrated Security=True;"
strSQL = "SELECT LastName, FirstName FROM EmployeeTable"
Dim da As New SqlDataAdapter(strSQL, strConn)
Dim ds As New DataSet()
da.Fill(ds, "AllEmployeesList")
For i As Integer = 0 To ds.Tables("AllEmployeesList").Rows.Count - 1
Dim row As DataRow = ds.Tables("AllEmployeesList").Rows(i)
cbAllEmployeesList.Items.Add(row("LastName") & ", " & row("FirstName"))
Next
End Sub
Now suppose I have a button on my Form ('GetAge') which is designed to retrieve the age of the employee selected in the combobox from the dataset "AllEmployeesList" and display in a TextBox on the same Form.
The bit I really don't understand is how I can interact with the original dataset that I have created to get the age? It seems to me that the dataset is only in memory during the Load event? If my dataset persists beyond the Load event then where can I find it?
My understanding is that a dataset object is an offline cache of data and has no links to the underlying database.This is useful as it allows you to manipulate the data without keeping a connection open and later on you can submit any changes in the Dataset back to the original database. So once I have built my dataset in the Load event how can I then further interact with it?
I suspect there is a large error in my understanding of what a Dataset object is. Can anybody set me straight?
Thanks to anybody who can help
Alex
A data set can hold multiple data tables, so if you fill that same dataset that already has the "AllEmployeesList" datatable filled, you can fill another datatable with the age under another table name. Picture a dataset as an in-memory database.
You can store the dataset in the datasource of the datagrid view, or make it a form level variable so you can interact with it without casting anytime.
Another part of datasets to be aware of is you can make a design-time dataset so things are more type-safe and explicit.
You seem to have a good grasp on the concept and reason of the DataSet. Your question is really more about managing state than the ins and outs of a DataSet.
You never stated if you are using WebForms, WinForms, or something else. If you're using WinForms, promote the DataSet to be a member variable of the form. It'll stay in memory as long as the form is open.
If you're using WebForms, then this becomes much more complex. This is a good overview to get you started.
Unless your application needs to operate in a disconnected mode, it's not strictly necessary nor always a good idea to cache database data on the client. In this case, you're extracting the age data for all employees without knowing whether you'll ever need it for any of them.
I would just pull the first and last name data (probably using SqlCommand.ExecuteReader) to populate the list box, and then make a separate call to the database to get the age if the user clicks the button. I posted an example of something similar using SqlCommand.ExecuteScalar on your other question.
When a Function or Sub has finished executing, all the variables you declared with the Dim statement will be gone. If you want a variable to exist as long as your form exists then declare a variable outside your Sub/Function:
Public Class frmEmployee
Private dsEmployeeList As DataSet
Private Sub frmEmployee_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
dsEmployeeList = New DataSet()
da.Fill(dsEmployeeList, "AllEmployeesList")
...
End Sub
Private Sub GetAge_Click(sender As Object, e As EventArgs) Handles GetAge.Click
Dim iRowIndex As Integer
iRowIndex = cbAllEmployeesList.SelectedIndex 'In this case the rownumber is the same as the index of the selected item in the combobox
'Check to see if an item from the combobox has been selected
If iRowIndex >= 0 Then
txtEmployeeAge.Text = dsEmployeeList.Tables("AllEmployeesList").Rows(iRowIndex).Item("Age").ToString()
End If
End Sub
This code might work but it's not a recommended solution. Like the previous poster said: only get the data you want, when you need it.
You should bind the DataGrid to the DataSet. When reqd you can retrieve the DataSet back from the DataGrid.DataSource and cast it to a DataSet.
Edit: Added sample code
DataSet ds = new DataSet();
// Code to populate DataSet from your DB
...
...
Assign ds to the datasource of data grid
this.dataGridView1.DataSource = ds;
To retrieve the dataset use the code below
DataSet retrievedFromGrid = (DataSet)this.dataGridView1.DataSource;
However, if you need to perform operations on this DataSet a number of times and memory is not an issue, I would suggest you store it in a class variable to avoid the overhead of casting in a DataSet object from the DataGrid again and again.

Refreshing BindingSource after insert (Linq To SQL)

I have a grid bound to a BindingSource which is bound to DataContext table, like this:
myBindingSource.DataSource = myDataContext.MyTable;
myGrid.DataSource = myBindingSource;
I couldn't refresh BindingSource after insert. This didn't work:
myDataContext.Refresh(RefreshMode.OverwriteCurrentValues, myBindingSource);
myBindingSource.ResetBinding(false);
Neither this:
myDataContext.Refresh(RefreshMode.OverwriteCurrentValues, myDataContext.MyTable);
myBindingSource.ResetBinding(false);
What should I do?
I have solved the problem but not in a way I wanted.
Turns out that DataContext and Linq To SQL is best for unit-of-work operations. Means you create a DataContext, get your job done, discard it. If you need another operation, create another one.
For this problem only thing I had to do was recreate my DataContext like this.dx = new MyDataContext();. If you don't do this you always get stale/cached data. From what I've read from various blog/forum posts that DataContext is lightweight and doing this A-OK. This was the only way I've found after searching for a day.
And finally one more working solution.
This solution works fine and do not require recreating DataContext.
You need to reset internal Table cache.
for this you need change private property cachedList of Table using reflection.
You can use following utility code:
public static class LinqDataTableExtension
{
public static void ResetTableCache(this ITable table)
{
table.InternalSetNonPublicFieldValue("cachedList", null);
}
public static void ResetTableCache(this IListSource source)
{
source.InternalSetNonPublicFieldValue("cachedList", null);
}
public static void InternalSetNonPublicFieldValue(this object entity, string propertyName, object value)
{
if (entity == null)
throw new ArgumentNullException("entity");
if(string.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("propertyName");
var type = entity.GetType();
var prop = type.GetField(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop != null)
prop.SetValue(entity, value);
// add any exception code here if property was not found :)
}
}
using something like:
var dSource = Db.GetTable(...)
dSource.ResetTableCache();
You need to reset your BindingSource using something like:
_BindingSource.DataSource = new List();
_BindingSource.DataSource = dSource;
// hack - refresh binding list
Enjoy :)
Grid Data Source Referesh by new query instead just Contest.Table.
Simple Solution < But Working.
Whre is eg.
!!!!! Thanks - Problem Solved after no of days !!! but with so simple way ..
CrmDemoContext.CrmDemoDataContext Context = new CrmDemoContext.CrmDemoDataContext();
var query = from it in Context.Companies select it;
// initial connection
dataGridView1.DataSource = query;
after changes or add in data
Context.SubmitChanges();
//call here again
dataGridView1.DataSource = query;
I have the same problem. I was using a form to create rows in my table without saving the context each time. Luckily I had multiple forms doing this and one updated the grid properly and one didn't.
The only difference?
I bound one to the entity similarly (not using the bindingSource) to what you did:
myGrid.DataSource = myDataContext.MyTable;
The second I bound:
myGrid.DataSource = myDataContext.MyTable.ToList();
The second way worked.
I think you should also refresh/update datagrid. You need to force redraw of grid.
Not sure how you insert rows. I had same problem when used DataContext.InsertOnSubmit(row), but when I just inserted rows into BindingSource instead BindingSource.Insert(Bindingsource.Count, row)
and used DataContext only to DataContext.SubmitChanges() and DataContext.GetChangeSet(). BindingSource inserts rows into both grid and context.
the answer from Atomosk helped me to solve a similar problem -
thanks a lot Atomosk!
I updated my database by the following two lines of code, but the DataGridView did not show the changes (it did not add a new row):
this.dataContext.MyTable.InsertOnSubmit(newDataset);
this.dataContext.SubmitChanges();
Where this.dataContext.MyTable was set to the DataSource property of a BindingSource object, which was set to the DataSource property of a DataGridView object.
In code it does looks like this:
DataGridView dgv = new DataGridView();
BindingSource bs = new BindingSource();
bs.DataSource = this.dataContext.MyTable; // Table<T> object type
dgv.DataSource = bs;
Setting bs.DataSource equals null and after that back to this.dataContext.MyTable did not help to update the DataGridView either.
The only way to update the DataGridView with the new entry was a complete different approach by adding it to the BindingSource instead of the corresponding table of the DataContext, as Atomosk mentioned.
this.bs.Add(newDataset);
this.dataContext.SubmitChanges();
Without doing so bs.Count; returned a smaller number as this.dataContext.MyTable.Count();
This does not make sense and seems to be a bug in the binding model in my opinion.

Resources