-1

I have a SQL Server instance that contains many databases. These databases all have the same table structure. I'm creating a C# app to connect to this server and then run stored procedures.

I created a stored procedure in one of the databases and my C# code connects to and runs it just fine. My question is: can I create a stored procedure that I can pass the database name to and have it run?

My C# is fairly straightforward. I created the Helper class to build my connection string:

public List<ProcessRuns> GetRunId(string selectedDB, string runId)
{
    using (IDbConnection connection = new Microsoft.Data.SqlClient.SqlConnection(Helper.CnnVal(selectedDB)))
    {
        var output = connection.Query<ProcessRuns>("dbo.GetRun @RunId", new { RunId = runId }).ToList();
        return output;
    }
}

Currently I am passing in the database name selectedDB to connect to and the run id 'runId' to search for.

My stored procedure is straight forward:

USE [RP_Reserved_1]
GO
/****** Object:  StoredProcedure [dbo].[GetRun]    Script Date: 02/13/2025 14:25:59 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[GetRun] 
    @RunId char(36) 
AS
    SELECT
        RunId, RunDate, ActualDate, StopDate 
    FROM
        ProcessRuns 
    WHERE
        RunId = @RunId

I connected to a table in the master database and simply ran this query.

SELECT     
    RunId, RunDate, ActualDate, StopDate
FROM         
    RP_Reserved_1.dbo.ProcessRuns

It did go to the other database and return the data. That makes me think it is possible but I really have no clue how to make it work. I want to avoid going to every database and create the exact same stored procedure in it.

My intent is to create one stored procedure and then pass what database I want it to run against.

So something like this:

USE @SelectedDB
GO
/****** Object:  StoredProcedure [dbo].[GetRun]    Script Date: 02/13/2025 14:25:59 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[GetRun] 
    @RunId char(36) 
AS
    SELECT
        RunId, RunDate, ActualDate, StopDate 
    FROM
        ProcessRuns 
    WHERE 
        RunId = @RunId

Then I could have this C# code call it:

var output = connection.Query<ProcessRuns>("dbo.GetRun @SelectedDB, @RunId", new { SelectedDB = selectedDB, RunId = runId }).ToList();

Is this possible and if so how?

3
  • 3
    You'd have to use dynamic SQL... and its definitely an anti-pattern. You're much better off adding it to all databases. If you are managing that many databases you should have some sort of version control and deployment method which would mean adding the same SP to all should take no additional dev time.
    – Dale K
    Commented Feb 14 at 0:28
  • 3
    A stored proc is buying you nothing useful here. Just run the code directly. This is a XY Problem - en.wikipedia.org/wiki/XY_problem .
    – mjwills
    Commented Feb 14 at 1:10
  • Side note: the correct way to call a procedure is connection.Query<ProcessRuns>("dbo.GetRun", new { SelectedDB = selectedDB, RunId = runId }), commandType: CommandType.StoredProcedure) and you should also consider using async and await Commented Feb 14 at 11:47

2 Answers 2

3

You could add the stored procedure to all databases, but that requires you to have change control in place that allows you to easily broadcast changes to all of the copies.

But if you need to loop to call against more than one, or any that aren't the current database context, you need some kind of dynamic control over where it gets called.

My preferred mechanism to do this is, well, I'm not sure what it's called, actually. But you can take advantage of the fact that EXEC takes a procedure name, and can accept a variable/parameter. So you can say up front "I want this to execute in the context of {database}," e.g.:

-- @RunId specified previously

DECLARE @database sysname = N'RP_Reserved_1';

IF DB_ID(@database) IS NOT NULL
BEGIN
  DECLARE @sql nvarchar(max) = N'
      SELECT RunId, RunDate, ActualDate, StopDate
        FROM dbo.ProcessRuns
         WHERE RunId = @RunId;',
          @exec nvarchar(512) = QUOTENAME(@database) + N'.sys.sp_executesql';

  EXEC @exec @sql, N'@RunId char(36)', @RunId;
--^^^^^^^^^^^^^^^
-- resolves to EXEC RP_Reserved_1.sys.sp_executesql @sql;
END

All the usual warnings about dynamic SQL always apply, of course.

8
  • But why use the stored proc at all? Why can't they just use invoke select RunId, RunDate, ActualDate, StopDate from {databasename}.dbo.ProcessRuns where RunId = @RunId directly from C#, passing @RunId as a parameter? Why faff about with stored procs or sp_executesql?
    – mjwills
    Commented Feb 14 at 3:11
  • 1
    @mjwills Because they asked how to do it in a stored procedure. Maybe the stored procedure is a lot more complex than the simple query they put in the question. Maybe it performs other logic and maybe that logic is conditional on the database passed, and they don't want the complicated query or any of that logic in the application code. Maybe their boss is a pointy-haired boss and said it has to be done in a stored procedure. It's something I've done in stored procedures plenty of times, so I'm not going to go out of my way to dismantle and change their approach. Commented Feb 14 at 3:14
  • Personally I'd not bother checking the DB_ID and just let it throw an exception. Otherwise it's going to silently exit and not return a resultset at all, which would be somewhat unexpected on the C# side. Or throw a custom exception using THROW. Commented Feb 14 at 11:48
  • @Charlieface Well, sure. I wasn't going to implement all their error handling too, I just always code defensively instead of blindly executing whatever @database happens to get passed in. Commented Feb 14 at 12:02
  • @mjwills This network (which I help manage) is built this way: 300+ databases with identical schemas. I often have to "do a thing against each database" and I can assure you my vehicle for doing that is not to write a C# program so I can benefit from "simple" parameterized queries. Sometimes I have to "do a thing" for all (or selected) databases based on a SQL Server Agent job, or manual, or some other team's C# app that I can't control. So, yes, there is value in having a single stored procedure that can execute the same or similar logic against many/arbitrary databases. Commented Feb 14 at 12:06
-3

The real answer for your situation - do not create a stored procedure. In situations like this, develop your system towards app-centric SQL processing. Multiple databases, multiple DB engine type etc will benefit from having your SQL code in repositories of your application.

You can replace stored procedures with prepared statements.

Call this from C#:

DECLARE @preparedId int;
EXEC sp_prepare @someInt OUTPUT,
    N'@prodId int',
    N'SELECT ProdKey, ProdName FROM Products WHERE prodId = @prodId;';
 
SELECT @preparedId;

And then use your prepared id

EXEC sp_execute 1, 10; -- where 1-prepared id and  10 is product id

-- destroy after use/app exiting

exec sp_unprepare 1;

Above is straight SQL but you can do it with C#. The only downside is that you prepare and you want to destroy. And your app and SQL Server are disconnected. If your app exits abruptly, there is no guarantee.

Or just execute SQL in code. You can have a special metadata DB or repository where these statements are stored and then only substitute connection.

Another thing, code like this example isn't even worth preparing, in the sense of performance gains. Only if this is some complex block that takes awhile to compile by the server. If you just have a select, just use parameterized query, which will be compiled and cached in the server buffer and every next use will be only value substitution.

Bottom line - don't do what you're doing.

15
  • 1
    Also FYI, its best practice to schema qualify all database objects e.g. ... FROM dbo.Products ...
    – Dale K
    Commented Feb 14 at 5:02
  • 4
    sp_prepare is utterly unnecessary. Just create a standard parameterised query.
    – mjwills
    Commented Feb 14 at 5:56
  • "The only downside is that you prepare and you want to destroy. And your app and SQL Server are disconnected. If your app exits abruptly, there is no guarantee." what does that even mean? What should be the issue here? Commented Feb 14 at 11:50
  • 1
    I've been using SQL Server since 6.5 and not once in my career have I used sp_prepare / sp_execute on purpose. I've only ever mentioned it here one time when someone was using it by mistake. Commented Feb 14 at 12:09
  • 1
    @DaleK Used your suggestion ^^
    – T.S.
    Commented Feb 14 at 21:27

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.