i have a question on how to work properly with related datatables and how to actualize them properly in the backend source database.
the situation is following:
I have a SQL database (SQL Server) which is the basic data source. In here the tables can have relation with each other via foreign keys.
this SQL database i use to fill datatables via SQL queries. The resulting datatables i use to visualize the information on the formular
var query = new SqlCommand(_sqlquery, connection);
var sqlAdapter = new SqlDataAdapter(query);
sqlAdapter.Fill(dataTable);
...
Here i am using sql queries to get the needed information into my datatables for example:
SELECT * FROM [order] INNER JOIN customer ON [order].customerID =
customer.customerID;
So far so good the the visualization in the datagrid is working well.
BUT the problems are rising when trying to save modifications on the visualized data in the datagrid back in the source sql database.
I searched a lot in the internet and the solution seems to be the usage of TableAdapterManager. The problem is that i don't really understand how to connect my code i have already to visualize the data with the TableAdapterManager which can actualize the data in the sql database.
Let me add another answer - it will be easy to explain step by step. I will edit/add more details when it needed.
1. start from creating new dataset and add datatables which you need.
Add new tableadapter - it will also create datatable based on your or default query. Tableadapter may have many queries - they used to fill the datatable from db.
Start from simple query:
select * from customers
or
select *, computedField1 = 'abc', computedField2 = 123
from customers
where 1=0
This "first" query is "fake" ("where 1=0" tell you that it will never be used) and declarative, from single table, no params. Usually you never retrieve ALL data w/o parameters or joins. This query "allows" the designer to create structure and update statements, recognise primary key.
Then add another queries:
FillById
select *
from customers
where custId = #custId
FillByName
select *
from customers
where custname like (#ss + '%')
FillByRecent
select cc.*
from customers cc
inner join orders oo on cc.custId = oo.custId // get only customers with orders
where oo.orderdate > #dtFrom
You will call them in code:
taCustomer.FillByName(ds.customers, "AAA"); // all started from "AAA"
Do the same for orders
2. Create relation
Click on gray space before column custId in Customer - it will select column. Drag selected and drop on custId in Orders
3. Add do form
After compile you dataset will be added to "Data Sources" window. Select your form, drag customer from "Data Sources" window to your form - it will create grig and add components to form: dataset, binding source, tableadapter.
Add orders too.
Look at the property "datasource" and "datamember" in the grids, and binding sources:
bsCustomer will refer to ds - customer
bsOrders will refer to customer - customers_orders_relation
4. Load / Update
in form_load() call
taOrders.FillBySomething(ds.orders, ...)
taCustomer.FillByAnotherSomething(ds.customers, ...);
under Save button
taOrders.Update(ds.orders); // will update only modified content
taCustomer.Update(ds.customers);
You use join. Its combine result from 2 tables which is good for view, but confusing for update (but it still possible). Your result will be as follow. What do you expect if user will do such modification:
ord1 cust1 --> cust1a
ord2 cust1 --> cust1b
ord3 cust2
ord4 cust2
usually if need updates, create 2 tables dtOrders and dtCustomers and set relation between them in c# (I like to use designer: add Dataset, add tableAdapters for orders and customers, drag customerId from one table to another to set relation). Drop on the form Dataset, Tableadapters, bindingSources, and cofigure orderDatasource to get data from customerDatasource->yourRelation. get data for each table.
Note. Using Dataset and tableadapters is more preferable for winform applications. For web usually used more lightweight constructions, to avoid recreation havy objects on each request.
customerTableAdapter.Fill(ds.dtCustomers);
ordersTableAdapter.Fill(ds.dtOrders);
Then you can display 2 grids (master-details) or one grig with using relation.
In your datatables you will not have duplicated customers do updates will not be confusing.
TableAdapterManager may be used if you need update tables in one transaction. If it is not important you may do updates without TableAdapterManager.
in other way you may use single table adapter with join and create your own update statemtes as many as you need:
ta.updateOrderMethod1(row1) // Update orders set ... where ordId = #ordId
ta.updateOrderMethod2(row1) // spUpdateOrder(ordId = #ordId)
ta.updateCustomer(row1) // Update customer set ... where customerId = #customerId
ta.updateCustomerNameOnly(row1) // Update customer set customerName=#customerName where customerId = #customerId
Related
I have a local report that I created in my application. It displays the contents of a table in my access database called "History". One of the columns is "ID" and it refers to the primary key in another table "Employees".
My report shows the employee's ID when it is refreshed but of course I want it to display the employee's name instead.
In short, I need to be able to perform a lookup on another table in my rldc file. How can I go about that?
I assume you fetch data from the database through a dataadapter and a typed dataset. something like this?
TestDataSetTableAdapters.CategoryTableAdapter ca = new TestDataSetTableAdapters.CategoryTableAdapter();
this.ds1 = new TestDataSet();
ca.Fill(this.ds1.Category);
Then you go to the dataset and modify the query in your tableadapter to something like this
select h.*,e.EmployeeName from history h
inner join Employees e on e.ID = h.UserID
Then the datset will be modified to include the column EmployeeName on all rows in the History table. Then the column "employeename" is directly available in yyour report
Here is an example of how to join your employee and history tables with SQL. My Access is rusty, so the syntax might not be perfect, but this should demonstrate the concept. Obviously, I made up the column names, so you will have to change the columns to whatever you need.
SELECT EmployeeID,
EmployeeName,
TimeIn,
TimeOut
FROM Employee Emp
INNER JOIN History History
ON Emp.EmployeeID = History.EmployeeID
I use asp.net 4 and DataSets for accessing the database. There are two tables with one-to-one relationship in the database. It means that both tables have the same column as a primary key (say Id), and one of tables has #identity on this column set.
So in general if we want to insert, we insert first into the first table, than insert into the second table with id.table2 = id of the corresponding record in table1.
I can imagine how to achieve this using stored procedure (we would insert into the first table and have id as an out parameter and then insert into the second table using this id, btw all inside one transaction).
But is there a way to do it without using a stored procedure? May be DataSets \ DataAdapters have such functionality built in?
Would appreciate any help.
Today it is so quiet here... Ok if anybody is also looking for such a solution, I've found a way to do it.
So our main problem is to get the id of the newly created record in the first table. If we're able to do that, after that we simply supply it to the next method which creates a corresponding record in the second table.
I used a DataSet Designer in order to enjoy the code autogeneration feature of the VS. Let's call the first table TripSets. In DataSet Designer right click on the TripSetsTableAdapter, then Properties. Expand InsertCommand properties group. Here we need to do two things.
First we add a new parameter into the collection of parameters using the Parameters Collection Editor. Set ParameterName = #TripId, DbType = Int32 (or whatever you need), Direction = Output.
Second we modify the CommandText (using Query Builder for convenience). Add to the end of the command another one after a semicolon like that:
(...);
SELECT #TripId = SCOPE_IDENTITY()
So you will get something like this statement:
INSERT INTO TripSets
(Date, UserId)
VALUES
(#Date,#UserId);
SELECT #TripId = SCOPE_IDENTITY()
Perhaps you will get a parser error warning, but you can just ignore it. Having this configured now we are able to use in our Business logic code as follows:
int tripId;
int result = tripSetsTableAdapter.Insert(tripDate, userId, out tripId);
// Here comes the insert method into the second table
tripSetTripSearchTableAdapter.Insert(tripId, amountPersons);
Probably you will want to synchronize this operations somehow (e.g. using TransactionScope) but it is completely up to you.
I want to fetch data from separate tables in sqlserver and display them together in GridView, Is that possible? if so then please suggest.
Thank you.
Yes, you can do that. Sample SQL is :
SELECT * FROM firstTable UNION ALL SELECT * FROM secondTable
Then you can get the results to datatable and bind that datatable to gridview.
Yes, that is possible, if both the returned result schemas are the same.
You would have to either join them at SQL level (with UNION) or at code level (with Enumerable.Union()).
Bottom line, you can only assign a single datasource to a control.
I can think in two ways of do it, the first is that the SQL query join the data in the desired way and return it merged.
The other is making a query that returns a DataSet with the 2 tables and merge in c# creating a third datatable via code with the columns we want and populating later from the obtained data.
I will use the first option if is posible..
Out of my lack of SQL Server experience and taking into account that this task is a usual one for Line of Business applications, I'd like to ask, maybe there is a standard, common way of doing the following database operation:
Assume we have two tables, connected with each other by one-to-many relationship, for example SalesOderHeader and SalesOrderLines
http://s43.radikal.ru/i100/1002/1d/c664780e92d5.jpg
Field SalesHeaderNo is a PK in SalesOderHeader table and a FK in SalesOrderLines table.
In a front-end app a User selects some number of records in the SalesOderHeader table, using for example Date range, or IsSelected field by clicking checkbox fields in a GridView. Then User performs some operations (let it be just "move to another table") on selected range of Sales Orders.
My question is:
How, in this case, I can reach child records in the SalesOrderLines table for performing the same operations (in our case "move to another table") over these child records in as easy, correct, fast and elegant way as possible?
If you're okay with a T-SQL based solution (as opposed to C# / LINQ) - you could do something like this:
-- define a table to hold the primary keys of the selected master rows
DECLARE #MasterIDs TABLE (HeaderNo INT)
-- fill that table somehow, e.g. by passing in values from a C# apps or something
INSERT INTO dbo.NewTable(LineCodeNo, Item, Quantity, Price)
SELECT SalesLineCodeNo, Item, Quantity, Price
FROM dbo.SalesOrderLine sol
INNER JOIN #MasterIDs m ON m.HeaderNo = sol.SalesHeaderNo
With this, you can insert a whole set of rows from your child table into a new table based on a selection criteria.
Your question is still a bit vague to me in that I'm not exactly sure what would be entailed by "move to another table." Does that mean there is another table with the exact schema of both your sample tables?
However, here's stab at a solution. When a user commits on a SalesOrderHeader record, some operation will be performed that looks like:
Update SalesOrderHeader
Set....
Where SalesOrderHeaderNo = #SalesOrderHeaderNo
Or
Insert SomeOtherTable
Select ...
From SalesOrderHeader
Where SalesOrderHeaderNo = #SalesOrderHeaderNo
In that same operation, is there a reason you can't also do something to the line items such as:
Insert SomeOtherTableItems
Select ...
From SalesOrderLineItems
Where SalesOrderHeaderNo = #SalesOrderHeaderNo
I don't know about "Best Practices", but this is what I use:
var header = db.SalesOrderHeaders.SingleOrDefault(h => h.SaleHeaderNo == 14);
IEnumerable<SalesOrderLine> list = header.SalesOrderLines.AsEnumerable();
// now your list contains the "many" records for the header
foreach (SalesOrderLine line in list)
{
// some code
}
I tried to model it after your table design, but the names may be a little different.
Now whether this is the "best practices" way, I am not sure.
EDITED: Noticed that you want to update them all, possibly move to another table. Since LINQ-To-SQL can't do bulk inserts/updates, you would probably want to use T-SQL for that.
When I built my .xsd, I had to choose the columns for each table, and it made a schema for the tables, right? So how can I get that Select string to use as a base Select command for new instances of dataadapters, and then just append a Where and OrderBy clause to it as needed?
That would keep me from having to keep each DataAdapter's field list (for the same table) in synch with the schema of that table in the .xsd file.
Isn't it common to have several DataAdapters that work on a certain table schema, but with different params in the Where and OrderBy clauses? Surely one does not have to maintain (or even redundently build) the field list part of the Select strings for half a dozen DataAdapters that all work off of the same table schema.
I'm envisioning something like this pseudo code:
BaseSelectString = MyTypedDataSet.JobsTable.GetSelectStringFromSchema() // Is there such a method or technique?
WhereClause = " Where SomeField = #Param1 and SomeOtherField = #Param2"
OrderByClause = " Order By Field1, Field2"
SelectString=BaseSelectString + WhereClause + OrderByClause
OleDbDataAdapter adapter = new OleDbDataAdapter(SelectString, MyConn)
Each table has a default query (The one on top with the check on it). When you dragged your tables in to the dataset to create the query, it wrote a SQL statement which it uses to schema your table. Keep that query simple, you might not actually use it in code, and you can always edit that query to update the table schema.
Every time you open the default query it connects to your datasource and allows you to select new columns that weren't in there before. If you want to update your existing columns, delete all the columns out of the table before you attempt to open the query. When you save the query, your updated columns get added back.
Make sure your connection string has permissions to view column information.
You can add multiple queries to a single TableAdapter. TableAdapters in the designer appear sectioned with a table schema at the top, and queries on the bottom. The default query will control which columns are available for output from the other queries. To add an additional query, right click on the TableAdapter and select "Add->Query" or if you are selecting the bottom part of the TableAdapter you can select "Add Query...". Any new SQL query you create will start off with the SQL from the default query. You give each new query a method name which you can use instead of the default query's "Fill" or "GetData" methods. The assumption is that each new query will have a result set that matches the default query even though they can have different "where" clause parameters.
In short
You may have a single TableAdapter for each table, just add multiple queries.
Each additional query can have different "Where" clause parameters as long as they all return the same columns.