MSDataSetGenerator using ExecuteScalar() instead of ExecuteNonQuery() - c#

I'm using a strongly-typed dataset (MSDataSetGenerator run on an XSD) to do some of the DB access in a project I'm working on, and I've hit upon a bit of a problem.
As I'm using Identity columns as the primary key of my tables I want to return the newly-generated ID when I call the Insert() method. However, the TableAdapter is being generated with the insert method as follows:
public int Insert(...stuff...)
{
// Sets up the command..
return this.InsertCommand.ExecuteNonQuery();
}
which returns the number of rows affected (i.e. 1).
This is despite the fact that the InsertCommandText is generated as:
INSERT INTO table VALUES (values...);
SELECT Id, ...stuff... FROM table WHERE (Id = SCOPE_IDENTITY());
Which can obviously be used to return the ID by instead doing the following:
public int Insert(...stuff...)
{
// Sets up the command..
return (int)this.InsertCommand.ExecuteScalar();
}
Does anyone know if there's a way to make the MSDataSetGenerator use the ExecuteScalar function as opposed to ExecuteNonQuery()? It seems odd that it would generate an insert command that selects the new data straight after the insert but then doesn't allow you to retrieve that data!
Thanks, Ed

Check the ExecuteMode whether Scalar or NonQuery in the properties of the query.

Related

Map identity value to object after merge statement

I have a table called People with the following schema:
Id INT NOT NULL IDENTITY(1, 1)
FirstName NVARCHAR(64) NOT NULL
LastName NVARCHAR964) NOT NULL
I am using a query like this one to perform inserts and updates in one query:
MERGE INTO People AS TARGET
USING ( VALUES
(#id0, #firstName0, #lastname0),
(#id1, #firstName1, #lastname1)
...
) AS SOURCE ([Id],[FirstName],[LastName])
ON TARGET.[Id] = SOURCE.[Id]
WHEN MATCHED BY TARGET THEN
UPDATE SET
[FirstName] = SOURCE.[FirstName],
[LastName] = SOURCE.[LastName]
WHEN NOT MATCHED BY TARGET THEN
INSERT ([FirstName],[LastName])
VALUES ([FirstName],[LastName])
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT $action, INSERTED.*;
My application is structured such that the client calls back to the server to load the existing state of the app. The client then creates/modifies/deletes entities locally and pushes those changes to the server in one bunch.
Here's an example of what my "SaveEntities" code currently looks like:
public void SavePeople(IEnumerable<Person> people)
{
// Returns the query I mentioned above
var query = GetMergeStatement(people);
using(var command = new SqlCommand(query))
{
using(var reader = command.ExecuteReader())
{
while(reader.Read())
{
// how do I tie these records back to
// the objects in the people collection?
}
}
}
}
I can use the value in the $action column to filter down to just INSERTED records. INSERTED.* returns all of the columns in TARGET for the inserted record. The problem is I have no way of distinctly linking those results back to the collection of objects passed into this method.
The only solution I could think of was to add a writable GUID column to the table and allow the MERGE statement to specify that value so I could link back to these objects in code using that and assign the ID value from there, but that seems like it defeats the purpose of having an automatic identity column and feels convoluted.
I'm really curious how this can work because I know Entity Framework does something to mitigate this problem (to be clear, I believe I'd have to same problem were I to be using a pure INSERT statement instead of a MERGE). In EF I can add objects to the model and call Entity.SaveChanges() and have the entity's ID property auto-update using magic. I guess it's that kind of magic I'm looking to understand more.
Also, I know I could structure my saves to insert one record at a time and cascade the changes appropriately (by returning SCOPE_IDENTITY for every insert) but this would be terribly inefficient.
One of the things I love about the MERGE statement is that the source data is in scope in the OUTPUT clause.
OUTPUT $action, SOURCE.Id, INSERTED.Id;
On insert, this will give you three columns: 'INSERT' in the first, the values of #id0 and #id1 in the second, and the matching, newly inserted Id values in the third.
In your C# code, just read the rows as you normally would.
while (reader.Read())
{
string action = reader.GetString(0);
if (action == "INSERT")
{
int oldId = reader.GetInt32(1);
int newId = reader.GetInt32(2);
// Now do what you want with them.
}
}
You can check for "DELETE" and "UPDATE" too, but keep in mind that ordinal 2 will be NULL on "DELETE" so you need to make sure you check for that before calling reader.GetInt32 in that case.
I've used this, in combination with table variables (OUTPUT SOURCE.Id, INSERTED.Id INTO #PersonMap ([OldId], [NewId])), to copy hierarchies 4 and 5 tables deep, all with identity columns.

Entity Framework 6 and SQL Server Sequences

I am using EF6 with a database first project. We have a requirement to use sequences which was a feature introduced in SQL server 2012 (I believe).
On the table the identity column has a default value set using:
(NEXT VALUE FOR [ExhibitIdentity])
This is used as we have two tables which store exhibit information for separate departments but we need the identity to be unique across both of the tables as it is then used as a reference in lots of other shared common tables.
My problem is using this within the Entity Framework, I have googled but couldn't find much information in relation to whether EF6 supports them. I have tried setting StoreGeneratedPatttern in the EFdesigner to Identity but when saving this complains that zero rows were affected as it is using scope_identity to see if the insert succeeded but as we are using sequences this comes back as null.
Setting it to computed throws an error saying I should set it to identity and setting it to none causes it to insert 0 as the id value and fail.
Do I need to call a function/procedure in order to get the next sequence and then assign it to the id value before saving the record?
Any help is much appreciated.
It's clear that you can't escape from this catch-22 by playing with DatabaseGeneratedOptions.
The best option, as you suggested, is to set DatabaseGeneratedOption.None and get the next value from the sequence (e.g. as in this question) right before you save a new record. Then assign it to the Id value, and save. This is concurrency-safe, because you will be the only one drawing that specific value from the sequence (let's assume no one resets the sequence).
However, there is a possible hack...
A bad one, and I should stop here...
EF 6 introduced the command interceptor API. It allows you to manipulate EF's SQL commands and their results before and after the commands are executed. Of course we should not tamper with these commands, should we?
Well... if we look at an insert command that is executed when DatabaseGeneratedOption.Identity is set, we see something like this:
INSERT [dbo].[Person]([Name]) VALUES (#0)
SELECT [Id]
FROM [dbo].[Person]
WHERE ##ROWCOUNT > 0 AND [Id] = scope_identity()
The SELECT command is used to fetch the generated primary key value from the database and set the new object's identity property to this value. This enables EF to use this value in subsequent insert statements that refer to this new object by a foreign key in the same transaction.
When the primary key is generated by a default taking its value from a sequence (as you do) it is evident that there is no scope_identity(). There is however a current value of the sequence, which can be found by a command like
SELECT current_value FROM sys.sequences WHERE name = 'PersonSequence'
If only we could make EF execute this command after the insert instead of scope_identity()!
Well, we can.
First, we have to create a class that implements IDbCommandInterceptor, or inherits from the default implementation DbCommandInterceptor:
using System.Data.Entity.Infrastructure.Interception;
class SequenceReadCommandInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(DbCommand command
, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
}
}
We add this class to the interception context by the command
DbInterception.Add(new SequenceReadCommandInterceptor());
The ReaderExecuting command runs just before command is executed. If this is an INSERT command with an identity column, its text looks like the command above. Now we could replace the scope_identity() part by the query getting the current sequence value:
command.CommandText = command.CommandText
.Replace("scope_identity()",
"(SELECT current_value FROM sys.sequences
WHERE name = 'PersonSequence')");
Now the command will look like
INSERT [dbo].[Person]([Name]) VALUES (#0)
SELECT [Id]
FROM [dbo].[Person]
WHERE ##ROWCOUNT > 0 AND [Id] =
(SELECT current_value FROM sys.sequences
WHERE name = 'PersonSequence')
And if we run this, the funny thing is: it works. Right after the SaveChanges command the new object has received its persisted Id value.
I really don't think this is production-ready. You'd have to modify the command when it's an insert command, choose the right sequence based on the inserted entity, all by dirty string manipulation in a rather obscure place. And I don't know if with heavy concurrency you will always get the right sequence value back. But who knows, maybe a next version of EF will support this out of the box.
This issue has been already solved:
https://github.com/dotnet/ef6/issues/165
A workaround for this issue was added in 8e38ed8. It requires setting
the following flag in the application:
SqlProviderServices.UseScopeIdentity = false; Here is the description
of the new flag (which includes some caveats worth knowing):
Gets or sets a value indicating whether to use the SCOPE_IDENTITY()
function to retrieve values generated by the database for numeric
columns during an INSERT operation. The default value of true is
recommended and can provide better performance if all numeric values
are generated using IDENTITY columns. If set to false, an OUTPUT
clause will be used instead. An OUTPUT clause makes it possible to
retrieve values generated by sequences or other means.

Insert method not behaving as expected

I've made my entire database for my application using only VS2012 express (I'm using localdb), so it handled creating the database as well as creating datasets and table adapters for my application to use. It's worked very well so far. I've also made wrapper classes to allow asynchronous database reads, which is also working great.
Looking at my table adapters in the designer, when I added my insert methods (for example on the Races table) it used the following query:
INSERT INTO Races (ChampionshipID, Driver1, Driver2, RaceTime)
VALUES (#ChampionshipID,#Driver1,#Driver2,#RaceTime);
SELECT ID, ChampionshipID, Driver1, Driver2, RaceTime FROM Races WHERE (ID = SCOPE_IDENTITY())
I see it does a select afterwards to return the row that was just inserted. That's perfect for what I need. However, the actual method it generates has the following signature:
int Insert1(long ChampionshipID, long Driver1, long Driver2, DateTime RaceTime)
It's returning an int and not the expected DataRow or DataTable, nor does it take a DataTable as a parameter to insert it into.
Am I doing something wrong? How can I implement this functionality?
The answer is no because, insert method uses the ExcecuteNonQuery method of ADO.NET, that's why your insert method is returning an int with the id of the record inserted.
You can try querying something like select ##identity just after inserting, so then you'll be able to get the record later.

Get ID of the newly inserted record, using SqlDataAdapter

BGREX.BGREXDataTable _bRexDataTable = new BGREX.BGREXDataTable();
BGREX.BGREXRow bgRexRow = _bRexDataTable.NewBGREXRow();
bgRexRow.BGRes_TITLE = "abc";
bgRexRow.BGRes_VERSION = 123;
_bRexDataTable.AddBGREXRow(bgRexRow);
int rewEffected = Adapter.Update(_bRexDataTable);
Have beed using above to insert record in Database, workes perfect. Now I need the id (Primary key) of newly inserted record which is auto generated, instead of rows affected to be returned from
Adapter.Update(_bRexDataTable);
It looks like I need SCOPE_IDENTITY() function, but I am not sure where to include this in designer as designers is not happy with following syntax added to the actual insert query.
It woks fine in sqlserver Management console though
DECLARE #ROWI_D int
INSERT INTO TABLE
.....
.....
.....
VALUES
("val1","val2",.......................)
SET ROW_ID = SCOPE_IDENTITY()
i'm not sure of a way to do this using sql statements with the commands. i'm not saying that it can't be done, i just never work with the statements, always through sprocs.
through the use of stored procedures you can define an output parameter which you can set equal to SCOPE_IDENTITY() in the sproc and then ensure that you pass that parameter into the command while settings it's direction as output.
sorry i don't have a solution for the direct sql call, but maybe someone else with more experience working in this way will chime in.
here's a link to a thread which discusses this same topic and a possible answer. but there's no verification whether the suggested solution worked or not.
Best way to do this is to change to a stored procedure that returns the value you want in a parameter. Post the code for doing the update you have now and I (or someone else) will show you how to refactor it to use a stored procedure. It is not hard.

Using SqlServer uniqueidentifier/updated date columns with Linq to Sql - Best Approach

Rightly or wrongly, I am using unique identifier as a Primary Key for tables in my sqlserver database. I have generated a model using linq to sql (c#), however where in the case of an identity column linq to sql generates a unique key on inserting a new record for guid /uniqueidentifier the default value of 00000000-0000-0000-0000-000000000000.
I know that I can set the guid in my code: in the linq to sql model or elsewhere, or there is the default value in creating the sql server table (though this is overridden by the value generated in the code). But where is best to put generate this key, noting that my tables are always going to change as my solution develops and therefore I shall regenerate my Linq to Sql model when it does.
Does the same solution apply for a column to hold current datetime (of the insert), which would be updated with each update?
As you noted in you own post you can use the extensibility methods. Adding to your post you can look at the partial methods created in the datacontext for inserting and updating of each table. Example with a table called "test" and a "changeDate"-column:
partial void InsertTest(Test instance)
{
instance.idCol = System.Guid.NewGuid();
this.ExecuteDynamicInsert(instance);
}
partial void UpdateTest(Test instance)
{
instance.changeDate = DateTime.Now;
this.ExecuteDynamicUpdate(instance);
}
Thanks, I've tried this out and it seems to work OK.
I have another approach, which I think I shall use for guids: sqlserver default value to newid(), then in linqtosql set auto generated value property to true. This has to be done on each generation of the model, but this is fairly simple.
There's two things you can do:
either just generate the GUID in your C# client side code and use that value
create a DEFAULT constraint on the GUID column in SQL Server that defaults to newid() for the column - the SQL Server will make SURE to always add a default - unless you specify a value yourself
As for the self-updating date/time columns - here you probably have to use either client-side logic to do that, or if you want to do it on SQL Server, you'll have to write a trigger. That's really the only way to update a specific column everytime the row gets updated - there's no "autoupdate" constraint or anything like this (and the default constraint only work on INSERTs, not updates).
Something like this might work:
CREATE TRIGGER TRG_UpdateDateTimestamp
ON (your table name)
AFTER UPDATE
AS
IF NOT UPDATE(DateTimeStamp)
BEGIN
UPDATE (yourtablename)
SET DateTimeStamp = GETDATE()
WHERE EXISTS (SELECT * FROM inserted AS i
WHERE i.OID = (yourtable).OID)
END
Marc

Categories