Refreshing BindingSource after insert (Linq To SQL) - winforms

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.

Related

Can't prevent a binding error

I have a datatable in my dataset that has these fields:
and I have a wpf window that the user inputs data into controls corresponding to these fields. The controls are bound to a datarow as follows:
DataContext = myApp.Tables("VehicleExpenses").NewRow
When I load the form at run time I get this error:
System.InvalidCastException was unhandled by user code
HResult=-2147467262
Message=Conversion from type 'DBNull' to type 'Long' is not valid.
and this is the line that throws the error (this is generated code):
Return CType(Me(Me.tableVehicleExpenses.CategoryIDColumn),Long)
So the error is obvious but I have no idea how to prevent VB from pulling a Null value from the new row. I've tried writing a blocking converter and setting UpdateSourceTrigger = Explicit but neither solves the problem.
--UPDATE 1--
Do not get confused by Dataset and this model. This middle layer is for user interaction and validation purpose. Saving data to db is in 2 stages. You will still need the auto generated properties and methods for CRUD operations.
Imagine this possibility (this is c#)
public class Vehicle
{
public int VehId{get;set;}
public Vehicle(){}
public bool SaveToDb()
{
MyDataSet myDataSet = new MyDataSet();
myDataSet.VehicleExpenses newVehRow = myDataSet.VehicleExpenses.NewVehicleExpensesRow();
newVehRow.VehId = this.VehId;
myDataSet.VehicleExpenses.Rows.Add(newVehRow);
// Save the new row to the database
myDataSet.Update(this.myDataSet.VehicleExpenses);
}
}
Delete the row with that Null value in the table directly (if present).
The DataTable.NewRow creates a new DataRow with column defaults values (since nothing is passed). Not sure why it's not setting default value as 0 for the non-nullable column
Try this option instead of myApp.Tables("VehicleExpenses").NewRow if possible
'declare a VehicleExpenses in the form (you might have to create this class)
Dim blankMo As VehicleExpenses
' in Form_load() set this object with new VehicleExpenses and against Datacontext
blankMo = New VehicleExpenses()
DataContext = blankMo
You will need to add a separate save button and function to commit this model details to the database.
Let us know..

SPARQL results in WPF (binding DataGrid)

I use dotnetrdf and I would like to display results from query in WPF. This is my function in ViewModel. I have DataTable which I use next in my view.
//Results
SparqlResultSet results;
DataTable table;
//Define a remote endpoint
//Use the DBPedia SPARQL endpoint with the default Graph set to DBPedia
SparqlRemoteEndpoint endpoint = new SparqlRemoteEndpoint(new Uri("http://dbpedia.org/sparql"), "http://dbpedia.org");
//Make a SELECT query against the Endpoint
results = endpoint.QueryWithResultSet("PREFIX dbo: <http://dbpedia.org/ontology/> PREFIX : <http://dbpedia.org/resource/> SELECT ?film ?producerName WHERE { ?film dbo:director :Andrzej_Wajda . ?film dbo:producer ?producerName . }");
foreach (SparqlResult result in results)
{
Console.WriteLine(result.ToString());
}
table = new DataTable();
DataRow row;
switch (results.ResultsType)
{
case SparqlResultsType.VariableBindings:
foreach (String var in results.Variables)
{
table.Columns.Add(new DataColumn(var, typeof(INode)));
}
foreach (SparqlResult r in results)
{
row = table.NewRow();
foreach (String var in results.Variables)
{
if (r.HasValue(var))
{
row[var] = r[var];
}
else
{
row[var] = null;
}
}
table.Rows.Add(row);
}
break;
case SparqlResultsType.Boolean:
table.Columns.Add(new DataColumn("ASK", typeof(bool)));
row = table.NewRow();
row["ASK"] = results.Result;
table.Rows.Add(row);
break;
case SparqlResultsType.Unknown:
default:
throw new InvalidCastException("Unable to cast a SparqlResultSet to a DataTable as the ResultSet has yet to be filled with data and so has no SparqlResultsType which determines how it is cast to a DataTable");
}
In WPF I use code:
<DataGrid ItemsSource="{Binding Table}" AutoGenerateColumns="True"/>
Binding work very well and finally I get dynamic created columns and DataGrid, but only header. I don't get value of rows. In this example there are rows, but without values.
Where is my problem ? Thanks a lot for help :)
This question is not really much to do with dotNetRDF other than the starting data is from a SPARQL query but is really about how DataGrid behaves when the ItemsSource is a DataTable and AutoGenerateColumns is used.
The basic problem is that the DataGrid does not know how to display arbitrary data types and it just generates DataGridTextColumn for the auto-generated columns. Unfortunately this only supports String values or types for which an explicit IValueConverter is applied AFAIK, it doesn't call ToString() because conversions are expected to be two way hence why you see empty columns (thanks to this question for explaining this).
So actually getting values to be appropriately displayed requires us to create a DataTemplate for our columns to use. However as you want to use AutoGenerateColumns you need to add a handler for the AutoGeneratingColumns event like so:
<DataGrid ItemsSource="{Binding Table}" AutoGenerateColumns="True"
AutoGeneratingColumn="AutoGeneratingColumn" />
Next you need to add an implementation of the event handler to apply an appropriate column type for each auto-generated column like so:
private void AutoGeneratingColumn(object sender, System.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType != typeof (INode)) return;
DataTableDataGridTemplateColumn column = new DataTableDataGridTemplateColumn();
column.ColumnName = e.PropertyName;
column.ClipboardContentBinding = e.Column.ClipboardContentBinding;
column.Header = e.Column.Header;
column.SortMemberPath = e.Column.SortMemberPath;
column.Width = e.Column.Width;
column.CellTemplate = (DataTemplate) Resources["NodeTemplate"];
e.Column = column;
}
Note the use of a special DataTableDataGridTemplateColumn type here, this is just the class from an answer to Binding WPF DataGrid to DataTable using TemplateColumns renamed to something more descriptive.
The reason we can't use DataGridTemplateColumn directly is that when binding a DataTable the template for each column is passed the entire row rather than the specific column value so we need to extend the class in order to bind only the specific column value so our template formats the actual INode value for that column in the row and not the whole row.
Finally we need to define the template we've referred to in our XAML so that our columns are appropriately formatted:
<Window.Resources>
<sparqlResultsDataGridWpf:MethodToValueConverter x:Key="MethodToValueConverter" />
<DataTemplate x:Key="NodeTemplate" DataType="rdf:INode">
<TextBlock Text="{Binding Converter={StaticResource MethodToValueConverter}, ConverterParameter='ToString'}"/>
</DataTemplate>
</Window.Resources>
Note that I've also defined a value converter here, this MethodToValueConverter is taken from an answer of Bind to a method in WPF? and allows us to simply take the result of a method call on an arbitrary type and use this as our display value. Here the configuration of our template simply calls ToString() on the underlying INode instances.
With all these things implemented I run your example query and I get the following in my DataGrid:
You can find all my code used at https://bitbucket.org/rvesse/so-23711774
You can use this basic approach to construct a much more robust rendering of INode with as many visual bells and whistles as you see fit.
Side Notes
A couple of notes related to this answer, firstly it would have been much easier to produce if you had posted a minimal complete example of your code rather than just partial XAML and code fragments.
Secondly the dotNetRDF SparqlResultSet class actually already has an explicit cast to DataTable defined so you shouldn't need to manually translate it to a DataTable yourself unless you want to control the structure of the DataTable e.g.
DataTable table = (DataTable) results;

How to detect the sender(button) of dynamical created bindings

I create some RibbonButtons dynamically and add them to a group according to an xml file. The follwoing function is carried out as often as entries found in the xml file.
private void ExtAppsWalk(ExternalAppsXml p, AppsWalkEventArgs args)
{
RibbonButton rBtn = new RibbonButton();
rBtn.Name = args.Name;
Binding cmdBinding = new Binding("ExtAppCommand");
rBtn.SetBinding(RibbonButton.CommandProperty, cmdBinding);
Binding tagBinding = new Binding("UrlTag");
tagBinding.Mode = BindingMode.OneWayToSource;
rBtn.SetBinding(RibbonButton.TagProperty, tagBinding);
rBtn.Label = args.Haed;
rBtn.Tag = args.Url;
rBtn.Margin = new Thickness(15, 0, 0, 0);
MyHost.ribGrpExtern.Items.Add(rBtn);
}
I tried to use the Tag property to store the Url's to be started when the respective button is clicked. Unfortunately the binding to the Tag property gives me the last inserted Url only.
What would be the best way to figure out which button is hit or to update the Tag property.
The datacontext is by default the context of the Viewmodel. The RibbonGroup to which the Buttons are added is created in the xaml file at designtime. I use that construct:
MyHost.ribGrpExtern.Items.Add(rBtn);
to add the buttons. It maight not really be conform with the mvvm pattern. May be someone else has a better idea to carry that out.
I foud a solution for my problem here and use the RelayCommand class. So I can pass objects (my Url) to the CommandHandler.
RibbonButton rBtn = new RibbonButton();
rBtn.Name = args.Name;
Binding cmdBinding = new Binding("ExtAppCommand");
rBtn.SetBinding(RibbonButton.CommandProperty, cmdBinding);
rBtn.CommandParameter = (object)args.Url;
private void ExtAppFuncExecute(object parameter)
{
if (parameter.ToString().....//myUrl

Entity Framework 4 Binding a related field to a DataGridView column

I have a DataGridView that is bound - via a binding source - to a list of entities:
VehicleRepository:
private IObjectSet<Vehicles> _objectSet;
public VehicleRepository(VPEntities context)
{
_context = context;
_objectSet = context.Vehicles;
}
List<Vehicle> IVehicleRepository.GetVehicles(Model model)
{
return _objectSet
.Where(e => e.ModelId == model.ModelId)
.ToList();
}
In my presenter
private List<Vehicle> _vehicles;
...
_vehicles = _vehicleRepository.GetVehicles(_model);
_screen.BindTo(_vehicles);
in my view
public void BindTo(List<Vehicle> vehicles)
{
_vehicles = vehicles;
if (_vehicles != null)
{
VehicleBindingSource.DataSource = _vehicles;
}
}
This works fine - my grid displays the data as it should. However, in the grid I am wanting to replace the ModelId column with a description field from the Model table. I've tried changing the binding for the column from ModelId to Model.ModelDescription but the column just appears blank.
I'm pretty sure that the data is being loaded, as I can see it when I debug, and when the same list is passed to a details screen I can successfully bind the related data to text fields and see the data.
Am I doing something obviously wrong?
It's a bit manual, but it 'works on my machine'.
Add a column to your DataGridView for the description field and then after you set your DataSource iterate through like so.
Dim row As Integer = 0
foreach (entity In (List<Entity>)MyBindingSource.DataSource)
{
string description = entity.Description;
MyDataGridView.Rows[row].Cells["MyDescriptionCell"].Value = description;
row ++;
}
You get a readonly view of your lookup. I make the new column readonly, but you could write something to handle the user changing the field if you wanted updates to run back to the server. Might be messy though.
The answer involves adding unbound read only columns and setting their value in the DataGridView's DataBindingComplete event
as described here
You can just add a column to your DataGridView, and in the DataPropertyName you must set the [entity].[Field name you need] in your case you could do: VehiclesType.Description
then you must add another binding source for the VehiclesTypes to the form, fill it using your context, and your good to go ;)

TDD with DataGridView

I'm relatively new to TDD, and still trying to learn to apply some of the concepts. Here's my situation.
I've got a WinForm with a DataGridView. I'm trying to write a test for the routine to be called by a button click that will perform some operations on the selected rows of the grid.
So I will be passing in the DataGridViewSelectedRowCollection object (i.e, the dgv.SelectedRows property at the time the button is clicked).
The DataGridViewSelectedRowCollection object has no constructor, so the only way I can figure to create it is to put together a DataGridView in my test project, then select some rows and pass in the SelectedRows property. But clearly, I don't want to re-create the whole form there.
So I do a DataGridView dgv = new DataGridView(), and gin up a BindingList (actually a SortableBindingList) just like the grid is bound to in the real application. The test list has 3 rows in it. And I do a dgv.DataSource = myList.
Now, at that point in the real application, the grid view is bound. If I look at dgv.Rows.Count, it's equal to the number of rows in the list. However, in my test, setting the DataSource property to the list still results in zero rows in the grid.
I'm thinking there's something missing in the creation of the gridview that normally gets done when it's added to the control list of the form. It probably initializes the handler for the OnDataSourceChanged event or something, and that isn't being done in my test code, but I'm really at a loss as to how to fix it, again, without re-creating a whole form object in my test fixture.
Here's the relavant code form my test method:
DataGridView residueGrid = new DataGridView();
List<Employee> baseListToGrid = new List<Employee>();
SortableBindingList<Employee> listToGrid = new SortableBindingList<Employee>(baseListToGrid);
residueGrid.DataSource = listToGrid;
for (int ix = 1; ix < 4; ix++)
{
listToGrid.Add(ObjectMother.GetEmployee(ix));
}
Assert.AreEqual(3, listToGrid.Count, "SortableBindingList does not have correct count");
Assert.AreEqual(3, residueGrid.Rows.Count, "DataGrid is not bound to list");
Thanks for any help you can give me.
DataGridView residueGrid = new DataGridView();
List<Employee> baseListToGrid = new List<Employee>();
SortableBindingList<Employee> listToGrid = new SortableBindingList<Employee>(baseListToGrid);
// residueGrid.DataSource = listToGrid; <-- move this line...
for (int ix = 1; ix < 4; ix++)
{
listToGrid.Add(ObjectMother.GetEmployee(ix));
}
// residueGrid.DataSource = listToGrid; <-- ...to here!
Assert.AreEqual(3, listToGrid.Count, "SortableBindingList does not have correct count");
Assert.AreEqual(3, residueGrid.Rows.Count, "DataGrid is not bound to list");
A useful structure for writing test is the following:
public void MyTest()
{
// Arrange
// Act
// Assert
}
In this case, Arrange would be instantiating all the objects, and filling the list. Act is where you set the data source of the gridview, and Assert is where you check that everything went OK. I usually write out those three comment lines each time I start writing a test.
Well, I solved the problem, and pretty much confirmed that it is something being done in the initialization of the control when added to the form that makes the DataSource binding work.
It suddenly dawned on me that that the "target" created by the MS testing framework is a private accessor to the Form itself. So I changed the line
DataGridView residueGrid = new DataGridView();
in the above code to, instead of creating a new DGV object, just reference the one on the target form:
DataGridView residueGrid = target.residueGrid;
That change made everything work as expected.

Resources