Domanda

Is it possible to perform an insert or update with the following constraints :

  1. Dapper is used in the project
  2. The Primary Key is auto-incrementing positive
  3. The newly inserted data may not have a PK value (or has a value of 0)
  4. The data needs to have the PK value on a newly inserted row.
  5. The query is being generated procedurally, and generalizing it would be preferable

Something like :

int newId = db.QueryValue<int>( <<insert or update>>, someData );

I have read about different solutions, and the best solution seems to be this one :

merge tablename as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

but

  1. it seems to fail on the third constraint and
  2. it does not guarantee to return the newly generated id.

Because of the 5th constraint (or preferance), a stored procedure seems overly complicated.

What are the possible solutions? Thanks!

È stato utile?

Soluzione

If your table has an auto-increment field, you can't assign a value to that field when inserting a record. OK you can, but it's normally a bad idea :)

Using the T-SQL MERGE statement you can put all of the values into the source table, including your default invalid identity value, then write the insert clause as:

when not matched then
    insert (field1, field2, ...)
    values (source.field1, source.field2, ...)

: and use the output clause to get the inserted identity value:

OUTPUT inserted.idfield

That said, I think you might be complicating your SQL code generation a little, especially for tables with a lot of fields. It is often better to generate distinct UPDATE and INSERT queries... especially if you've got some way of tracking the changes to the object so that you can only update the changed fields.

Assuming you're working on MS SQL, you can use SCOPE_IDENTITY() function after the INSERT statement to get the value of the identity field for the record in a composite statement:

INSERT INTO tablename(field1, field2, ...)
VALUES('field1value', 'field2value', ...);
SELECT CAST(SCOPE_IDENTITY() AS INT) ident;

When you execute this SQL statement you'll get back a resultset with the inserted identity in a single column. Your db.QueryValue<int> call will then return the value you're after.

For standard integer auto-increment fields the above is fine. For other field types, or for a more general case, try casting SCOPE_IDENTITY() result to VARCHAR(MAX) and parse the resultant string value to whichever type your identity column expects - GUID, etc.

In the general case, try this in your db class:

public string InsertWithID(string insertQuery, params object[] parms)
{
    string query = insertQuery + "\nSELECT CAST(SCOPE_IDENTITY() AS VARCHAR(MAX)) ident;\n";
    return this.QueryValue<string>(insertQuery, parms);
}

And/or:

public int InsertWithIntID(string insertQuery, params object[] parms)
{
    string query = insertQuery + "\nSELECT CAST(SCOPE_IDENTITY() AS INT) ident;\n";
    return this.QueryValue<int>(query, parms);
}

That way you can just prepare your insert query and call the appropriate InsertWithID method to get the resultant identity value. That should satisfy your 5th constraint with luck :)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top