im working with Entity Framework, SQL and C#.
i have a Table called Client and other called clients_phone.
I have a form with a Xtragrid and using BindingSource I bind the IQueryable to the grid.
myBindingSource = new BindingSource();
myBindingSource.DataSource = clients; //Clients it is the IQueryable<Client>
myBindingSource.DataMember = "clients_phone";
myBindingSource.AllowNew = true;
Then, i wan to add a new clients_phone to my client. To do this, i make a New Client() and then add the Phone.
clients newclient = objContext.CreateObject<clients>();
newclient.clients_phone = newClients_Phone;
objContext.AddObject("Clients", newclient);
Finally i add the new clients_phone in the ObjectContext, but when i see the Xtrag clients_phone don't show.
Any idea of what happens??.
Thanks
Lucas B is right. Whenever you've gotten round to adding all the new Clients and Clients_phone's you need to call the SaveChanges() method in order to persist the data into the database.
Whether you need to create the client first and then do an update to add further client_phone entries or whatever. If you never call SaveChanges() method nothing you do will be saved back to the database.
Have you tried saving and commiting the object?
objContext.SaveChanges(true);
objContext.AcceptAllChanges();
I have done the SaveChanges() but I see it as a workaround.
Drawback of this: When the user cancels the action the new object will be in the DB.
Use case: "Create person"
User select a menu item (or button or whatever) named "Create person" (and the new user should appear in a GridView control for example - SaveChanges() is called)
The the user fills in First Name, Last name etc.
But then the users discovers that he does not need to create that person (e.g. the user remembers that he has created that person already yesterday)
The user does not press the Save button (menu item or whatever ;-)) he uses the "Cancel" menu item (or button or whatever) - and so there will be an orphan person in the DB
Other approach: In order to add the new person (not committed yet) to a GridView control you can set the DataSource to "current persons + new person"
private object GetBindingList(ObjectSet<Person> contextObjects, Person newObject)
{
List<Person> list = new List<Person>();
list.AddRange(contextObjects);
list.Add(newObject);
return list;
}
Usage:
PersonsBindingSource.DataSource = GetBindingList(Context.PersonSet, newPerson);
But this has a disadvantage:
It works only the first time ... So you need to to something like:
PersonsBindingSource.DataSource = GetBindingList(Context.PersonSet, newPerson);
Context.PersonSet = Populate with persons from PersonsBindingSource.DataSource // ;-)
But why is contextObjects.AddObject(newObject); not working (the new item will not be displayed in the GridView - the problem only occurs for objects without foreign keys to other objects)
Also works only when calling SaveChanges():
PersonsBindingSource.DataSource = Context.PersonSet.Execute(MergeOption.AppendOnly);
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 have a project where a user can add new products or modify existing ones. Products can have parts associated with them. I created a list in the class where my product constructor lives to hold parts for each product.
I'm trying to figure out how to set up that if a user makes changes to the parts that are associated with a product but hits the cancel button then the list reverts to the original list. If they hit the save button after making edits to the products parts list then the updated list is saved and when they open the product again then the updated list displays.
All the code in the file I have is quite a bit but the parts I thought would be most helpful for what I have are...
Filling in the product text boxes with the information on the product. Populating the datagridview CurrentPartsDataGrid with the tempParts list that is a copy of the product.Parts list. Or creating a new empty list if it's a new product that is created.
if (product != null)
{
ProductIdText.Text = product.ID.ToString();
ProductNameText.Text = product.Name.ToString();
InvText.Text = product.QOH.ToString();
PriceText.Text = product.Price.ToString();
InvMinText.Text = product.Min.ToString();
InvMaxText.Text = product.Max.ToString();
tempParts = new BindingList<Part>(product.Parts);
}
else
{
product = new Product();
tempParts = new BindingList<Part>();
}
CurrentPartsDataGrid.DataSource = tempParts;
In the save/cancel button click event methods I have tried doing a for loop or for each loop. I clear the list and then try to repopulate.
Code in save
product.Parts.Clear();
foreach (Part part in tempParts)
{
product.Parts.Add(part);
}
Code in Cancel
tempParts.Clear();
foreach (Part part in product.Parts)
{
tempParts.Add(part);
}
If there is anything else that would be helpful, let me know. I'm new to posting here so don't want to overload the post but also don't want to not provide enough.
Any help on how to fix this would be awesome.
Thanks!
I ended up not needing to do any of the EndEdit() stuff.
In my (if product != null)
I added
tempList = new BindingList<Part>();
for (int i = 0; i < product.Parts.Count; i++)
{
tempList.Add(product.Parts[i]);
}
This created a new templist that when the user selected cancel I set product.Parts = tempList which resolved the issue.
I have a silverlight 4 application which uses RIA with EF to query multiple tables in a single DomainContext. BUGroup, BUGroupBuilding and vwBusinessUnit.
The UI basically loads the BUGroup entity set and I can select different BUGroups and it would load child tables like this:
I have a DomainContext that I'm passing to a childwindow in the Manage Buildings button like this:
ManageBuildingsChildWindow ManageBuildingscw = new ManageBuildingsChildWindow();
ManageBuildingscw.Closed += new EventHandler(ManageBuildingscw_Closed);
ManageBuildingscw.DataContext = null;
ManageBuildingsViewModel ManageBuildingsViewModel = new ManageBuildingsViewModel();
ManageBuildingscw.DataContext = ManageBuildingsViewModel;
and then I'm loading the childwindow context in the childwindow view model like this:
GetBUGroupResult = SecurityDomainContext.Current.Load(SecurityDomainContext.Current.GetBUGroupsCustomQuery(), LoadBehavior.RefreshCurrent, false);
GetBUGroupResult.Completed += new EventHandler(GetBUGroupResult_Completed);
here's the event handler for GetBUGroupResult
void GetBUGroupResult_Completed(object sender, EventArgs e)
{
GetBUGroupBuildings = SecurityDomainContext.Current.BUGroupBuildings.Where(w => w.BUGroupID == BUGroupID).ToList();
GetBUGroupResult.Completed -= new EventHandler(GetBUGroupResult_Completed);
}
I bind each BUGroupBuilding to a delete link in a datagrid and it deletes from database fine. when I click on the manage building button to invoke the child window and it loads fine for the first time. if i have 5 buildings, it loads 5 buildings. the problem is when i loads it 2nd or other times after deleting a few buildings. it retains the old DomainContext even after the load.
I even try setting the LoadBehavior to RefreshCurrent on the Load for the GetBUGroupsCustomQuery()
say I have 5 buildings in a group and i deleted 2 on parent window using the delete link so now i have 3. invokes the childwindow. it still shows 5.
Now I put a break on the DomainServices for GetBUGroupsCustomQuery() and i get the correct 3 value coming back
But during the GetBUGroupResult_Completed event handler, I'm seeing 5 buildings still. It looks like my DomainContext is not refreshing even when I specified the loadbehavior to refresh current.
any input?
I had a similar problem to this and it was solved with a workaround that loads data into the context and then detaches any objects in the entity collection that is not in the collection of the newly returned objects. Try something like this with your load operation:
SecurityDomainContext.Current.Load<YourObjectType>(
SecurityDomainContext.Current.GetBUGroupsCustomQuery(),
LoadBehavior.MergeIntoCurrent,
loadOperation =>
{
var results = context.Comments.Where(
entity => !loadOperation.Entities.Contains(entity)).ToList();
results.ForEach(entity => context.Comments.Detach(entity));
}, null);
I'm not sure if you'll need to replace <YourObjectType> with the entity type that is returned, or if you can just remove that part, but this should at least get you close.
You can also do:
var c = SecurityDomainContext.Current;
var group = c.BUGroups.Single(w => w.BUGroupID == BUGroupID);
c.Refresh(RefreshMode.StoreWins, group.BUGroupBuildings);
GetBUGroupBuildings = group.BUGroupBuildings.ToList();
By RefreshMode.StoreWins it is guaranteed that the current state of the database is retrieved.
Here is the scenario (ADO.NET Entity Framework and C#).
Contact*:
String name;
Address addr;
Address*:
String street;
String city;
**this is not the real code, but you get the picture*
I am trying to create a Windows Form that appears flat to the user. In other words, the Form will have four fields (name,addr,street,city), and when the users clicks on the Add button in a bindingSourceNavigator, I want to create a new instance of Contact such that Contact.addr is a reference to a newly created Address.
If I were only working with objects this would be simple, but I'm trying to create a new row in the table that backs Address.
Here is what I've tried so far:
private void contactBindingSource_AddingNew(object sender, AddingNewEventArgs e)
{
Contact newContact = new Contact();
Address newContactAddr = new Address();
newContact.Address = newContactAddr;
newContactAddr.Contacts.Add(newContact);
//I realize I don't need the Contact list reference in Address,
//but VS2010 created it, so I'm just adding the new Contact to
//the list for now.
e.NewObject = newContact;
}
private void contactBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
contactBindingSource.EndEdit();
context.SaveChanges(); //throws UpdateException
}
Some background: The Form has a binding source for Contact, and this method is the event handler for when new Contacts are created. I read on MSDN that this is how one modifies the object before it is actually added to the BindingSource. context refers to my entity model.
What happens: When I click the add button, I am able to enter in the contact information. But when I click the save button, I get an UpdateException. I suspect this is because I did not create the Address properly, but being new to the ADO.NET framework (and .NET programming in general), I don't really know the correct way to do this.
An example:
I have 3 tables, Users, UserRoles and Roles.
when i create a user i want this user to receive a role and i would then do
using(DatabaseEntities db = new DatabaseEntities())
{
//creates the user and add the properties except roles
Users user = new Users();
user.username = "Test";
//get an existing role
var role = db.Roles.SingleOrDefault(r => r.roleName == "User");
//adds the userid and roleid in to userRoles
user.Roles.Add(role);
db.Users.AddObject(user);
//saves it to the db
db.SaveChanges();
}
so, in order for it to work in your example, you would first need to insert One of them to the db before using it in order to save the other object along with the row to the table that links them together.
I hope this simple example helps you.
Given a DataGridView that has a BindingSource set like this:
On a winform, we add a BindingSource object using the designer, called myBindingScource.
Then on the Form.Designer.cs we add this to the InitializeComponents()
myBindingSource.DataSource = typeof(MyLinq.Person); //Mylinq is the autogenerated Linq Model/Diagram
Later, in the form itself we do:
myDataView.DataSource = myBindingSource;
and then we have a method that populates the Grid…
using ( myDataContext mdc = new MyDataContext() )
{
myDataView.DataSource = from per in mdc.person
select per;
}
As an aside note, I've set up the columns in Design Time, and everything shows ok.
Since the LINQ 2 SQL is not returning an Anonymous, the "myDataView" is editable, and here comes the question…
Question is: how do I persist those changes?
There are dozens of events in the datagrid, and I'm not sure which one is more appropriate. Even if I try one of the events, I still don't know what is the code I need to execute to send those changes back to the DB in order to persist the changes.
I remember back in the ADO.NET DataSet days, you would do dataadapter.Update(dataset);
Also imagine that both the retrieve and the persist() are on a Business Layer and the method signature looks like this:
public void LoadMyDataGrid(DataGridView grid);
that method takes the form's grid and populates it using the LINQ2SQL query shown above.
Now I'd like to create a method like this:
public void SaveMyDataGrid(DataGridView grid); // or similar
The idea is that this method is not on the same class (form), many examples tend to assume that everything is together.
RowValidated event would be a good place to check to see if it's time to persist changes to the database.
this.dataGridView1.RowValidated += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_RowValidated);
private void dataGridView1_RowValidated(object sender, DataGridViewCellEventArgs e)
{
MyLinq.Person person = dataGridView1.Rows[e.RowIndex].DataBoundItem as MyLinq.Person;
if (person != null)
{
// save this person back to data access layer
}
}
After your edit:
I wouldn't pass back a datagrid instance to your service layer. I'd pass back IEnumerable<MyLinq.Person> or IList<MyLinq.Person> then iterate over the collection in your service layer, and depending on the logic performed; persist the changes to the data access layer (your database)
The 'save' method on the DataContext object is SubmitChanges().
using (MyContext c = new MyContext())
{
var q = (from p in c.People
where p.Id == 1
select p).First();
q.FirstName = "Mark";
c.SubmitChanges();
}
As Michael G mentioned, you'll need to gather the changes, and pass them back to the bll object.