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.
Related
I have three tables with data as shown in the following screenshot:
I have a working MERGE statement for the Users table that is sent to the SQL Server from C#. The MERGE statement correctly merges data into the ICS_USERS table. The #temp table is also created and populated in C#:
MERGE INTO ICS_Users AS Target
USING #temp AS Source ON TARGET.USerID = Source.UserId
WHEN MATCHED THEN
UPDATE
SET TARGET.UserName = Source.UserName, TARGET.Active = Source.Active
WHEN NOT MATCHED THEN
INSERT(UserName, Active, UserInitials)
VALUES(Source.UserName, Source.Active, Source.UserInitials)
I want to allow the user to add/change/delete the role for a user and send in the MERGE statement to handle it. Note that the user will only ever be allowed to change a single user at one time.
How do I change the merge statement to account for the Role and User/Role associative table?
You can follow these steps to achieve this.
Create a stored procedure which will accept all the input parameters which are required to join, insert, update and delete the values using source and target. Make all parameter optional or null able so that you can run your merge even without input values.
Insert all these values in a table variable which will have same column as your target tables along with the same data type.
Use your actual source table and along with table variable and union them and then use the output in merge logic.
This will handle your both requirement in single merge statement. Even when you input columns will be null first query in union will always have values to suffice the merge logic.
Try if this will not work comment and I will provide you sql script. Here I am not providing sql intentionally so that at least you can read and try by your self and write the logic in sql.
(Sorry for my bad English)
I have imported an access database to a C# winform project (.net 4.0) in visual studio 2013. It automatically creates a .cs file with a DataSet, TableAdapter and a TableAdapterManager.
I import data from the database to the DataSet, without error. I succeed to manipulate data, and save change to the database with TableAdapterManager.UpdateAll().
But now I try to insert new data, with relation between tables.
For example, a database like mine
Parent table :
autonum key
string parentname
Child table
autonum key
string childname
int parentKey
First try :
I create a new record with parentTable.AddparenttableRow(data ...) and get a parentRow.
I create a new record with childTable.AddchildtableRow(parentRow, data ...)
But if I call TableAdpaterManager.UpdateAll(), I get an error "can't add or modify a record because a related record is required in parentTable" (not the real message, it's a translation). I think that AddchildtableRow create the correct relation. And another problem appears : because of the error, the database isn't modified (which is good), but the records I had add, are always in the table of the DataSet.
So I try another method : TableAdpaterManager.tablenameTableAdpater.Insert()
First I insert a parentRow without any problem. But when I want to insert a childRow, the insert function asks for the parent key. But I don't have it (the insert parent call doesn't return the key).
My question is : how can I use the DataSet, TableAdapter and TableAdapterManager to insert records in the DataSet AND in the database, and with a transaction (if there is an error, the data won't be written to the database, and won't be added to the DataSet) ? And actually, how to correctly use these classes ?
Look up the typed dataset code. Switch between the default TableAdapterManager.UpdateOrderOption.InsertUpdateDelete to UpdateInsertDelete (msdn). For hierarchical updates you have to merge new values for your identity columns (msdn). Also see this post. The way ADO.NET deals with preventing collisions with it's disconnected dataset, it assigns negative IDENTITY column values, because it wouldn't know a possible positive number that IS NOT a collision as it's disconnected. Also managing a ##identity crisis with parent-child relations. The typed dataset technology also had issues with circular table references.
The issue is like , I got two tables X & Y. When records are added to table X , columns should be added to the table Y in parallel.
http://prntscr.com/3owqfe <-- Provides a clear Idea.
I tried it with triggers , but seems like the triggers doesnt allow CREATE TABLE or ALTER TABLE. Anyway as I'm using Linq , Im trying to achieve that via Linq. Any suggestions ?
Edited: For the record the below trigger worked , but with an exception.
create trigger AddItemToCommon ON [SEP].[dbo].[ItemMaster]
FOR INSERT
AS BEGIN
declare #PAYID varchar(max)
select #PAYID = payCode from INSERTED
ALTER TABLE [SEP].[dbo].[CommonPayrollItems]
ADD sampleCol varchar(max)
END
Anyway it will only run once because , there cannot be more than 1 column of the same name. But if I can retrieve the row values grom ItemMaster table , it's still possible with triggers. Which I tried to replace sampleCol with #PAYID which results in Syntax error.
Linq does not have functionality to create or alter tables. You could however use the underlying connection of your Linq Datacontext to execute any SQL statement:
DataContext.ExecuteCommand(...)
And use that to run your alter table statement. This will not update your Linq model of that table though.
If you plan to do this at runtime, you might want to reconsider your data model and create a third table (eg. STable2Extended) that contains a foreign key to both STable and STable2 combined with the applicable value for that column.
You could then add the STable2Extended to the datamodel and query it easily when you need the extended properties on STable2 without dynamically modifying tables.
I have created two threads in C# and I am calling two separate functions in parallel. Both functions read the last ID from XYZ table and insert new record with value ID+1. Here ID column is the primary key. When I execute the both functions I am getting primary key violation error. Both function having the below query:
insert into XYZ values((SELECT max(ID)+1 from XYZ),'Name')
Seems like both functions are reading the value at a time and trying to insert with the same value.
How can I solve this problem.. ?
Let the database handle selecting the ID for you. It's obvious from your code above that what you really want is an auto-incrementing integer ID column, which the database can definitely handle doing for you. So set up your table properly and instead of your current insert statement, do this:
insert into XYZ values('Name')
If your database table is already set up I believe you can issue a statement similar to:
alter table your_table modify column you_table_id int(size) auto_increment
Finally, if none of these solutions are adequate for whatever reason (including, as you indicated in the comments section, inability to edit the table schema) then you can do as one of the other users suggested in the comments and create a synchronized method to find the next ID. You would basically just create a static method that returns an int, issue your select id statement in that static method, and use the returned result to insert your next record into the table. Since this method would not guarantee a successful insert (due to external applications ability to also insert into the same table) you would also have to catch Exceptions and retry on failure).
Set ID column to be "Identity" column. Then, you can execute your queries as:
insert into XYZ values('Name')
I think that you can't use ALTER TABLE to change column to be Identity after column is created. Use Managament Studio to set this column to be Identity. If your table has many rows, this can be a long running process, because it will actually copy your data to a new table (will perform table re-creation).
Most likely that option is disabled in your Managament Studio. In order to enable it open Tools->Options->Designers and uncheck option "Prevent saving changes that require table re-creation"...depending on your table size, you will probably have to set timeout, too. Your table will be locked during that time.
A solution for such problems is to have generate the ID using some kind of a sequence.
For example, in SQL Server you can create a sequence using the command below:
CREATE SEQUENCE Test.CountBy1
START WITH 1
INCREMENT BY 1 ;
GO
Then in C#, you can retrieve the next value out of Test and assign it to the ID before inserting it.
It sounds like you want a higher transaction isolation level or more restrictive locking.
I don't use these features too often, so hopefully somebody will suggest an edit if I'm wrong, but you want one of these:
-- specify the strictest isolation level
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
insert into XYZ values((SELECT max(ID)+1 from XYZ),'Name')
or
-- make locks exclusive so other transactions cannot access the same rows
insert into XYZ values((SELECT max(ID)+1 from XYZ WITH (XLOCK)),'Name')
I'm having such a problem: got dataset with a table(s). Say, we have already several records in a table which has a primary key (autoincrement). Basically, program works fine until I insert a new row. Even I use method AcceptChanges or Update, the new row is commited but it existing datatable object lacks the newest primary key value which I need for furhter processing. Is the only method to reload all the table from database again or there is a better way for it?
Thanks,
Ray
You should add ';select SCOPE_IDENTITY()' to the end of your insert sql statement attached to your data adapter. That will read back the last inserted value generated by autoincrement and update your dataset.
I.e, your insert sql should resemble this:
insert into sometable(column1,column2) values(#column1,#column2); select * from sometable where primarykeycolumn=scope_identity()
Your existing dataset will then contain the primary keys when dataadaper.Update(dataset) returns.
If you have a connection object you could use
int = (int)conn.executeScalar("SELECT ##IDENTITY")
Modified by what datatype your primary key is.
This has to be done directly before any other operations as a new insert will replace the stored value in ##IDENTIY