fiogf49gjkf0d
In an earlier article I demonstrated how to generate SalesLogix IDs in SQL using the CLR. The method I described still does work but I have found a few issues with it since it has been in production. The primary issues are:
- A 32 bit SQL Server is required because there is not a 64 bit SLX OLEDB driver.
- A 32 bit SQL Server has a relatively small amount of memory that the CLR can run in. If a large number of IDs are generated, it is possible to consume all this memory and essential cause SQL to crash.
- Adding an assembly with unsafe permission is not the best idea. I had that in there for testing and failed to remove it before publishing the article.
This article will address the three issues above and still provide the same basic functionality.
All three issues can be avoided by not using the CLR. To get around the CLR we are going to create a completely separate command line executable (using essentially the same code as before). This executable will take the table name and the number of IDs to generate as parameters (it also takes the database name as a parameter for an extra level of safety), and return the number of IDs by simply writing the IDs to the console. This executable can then be called using xp_cmdshell to retrieve the results. So, here is the code to this handy executable:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.OleDb;
using System.Collections;
namespace SalesLogix_GetNextID
{
class Program
{
static void Main(string[] args)
{
string DatabaseName,TableName;
int IDCount;
if (args.Length == 3)
{
DatabaseName = args[0].ToString();
TableName = args[1].ToString();
IDCount = Convert.ToInt32(args[2]);
}
else
{
DisplayHelp();
return;
}
SalesLogix_GetNextID.Properties.Settings gnidsetting = SalesLogix_GetNextID.Properties.Settings.Default;
OleDbConnection conn = new OleDbConnection(gnidsetting.SLXConnString);
ArrayList resultCollection = new ArrayList();
switch (TableName.ToUpper())
{
case "ATTACHMENT": TableName = "FILEATTACH"; break;
case "USERNOTIFICATION": TableName = "USERNOTIFY"; break;
case "AGENTS": TableName = "HOSTTASK"; break;
case "RESOURCELIST": TableName = "RESOURCE"; break;
case "USEROPTION": TableName = "USERVIEW"; break;
case "JOINDATA": TableName = "JOIN"; break;
case "PROCEDURES": TableName = "PROCEDURE"; break;
case "SEC_FUNCTIONOWNER": TableName = "FUNCTIONHANDLER"; break;
}
OleDbCommand cmd = new OleDbCommand(string.Format("slx_dbids('{0}', {1})", TableName, IDCount), conn);
OleDbCommand dbcheck = new OleDbCommand("SELECT DB_NAME()",conn);
OleDbDataReader r;
conn.Open();
/* this is just a saftey check. We want to make sure we are returning IDs for the correct database */
if (dbcheck.ExecuteScalar().ToString() == DatabaseName)
{
r = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
int i = 0;
while (r.Read())
{
/* do not add a return on the first one */
if (i == 0)
Console.Write(r.GetString(0));
else
Console.Write("\n" + r.GetString(0));
i++;
}
r.Dispose();
}
else
{
Console.Write("Database names do not match, check the config file.");
}
cmd.Dispose();
dbcheck.Dispose();
conn.Dispose();
}
static void DisplayHelp()
{
Console.WriteLine("Paramaters:");
Console.WriteLine(" DatabaseName - The SalesLogix database name, this is just a safty check");
Console.WriteLine(" TableName - The SalesLogix table name without schema name");
Console.WriteLine(" IDCount - The number of IDs to return");
Console.WriteLine("\nExample to return 5 ACCOUNTIDs:");
Console.Write(" SalesLogix_GetNextID.exe SLXSZ_DEV ACCOUNT 5");
}
}
}
As you can see there really is not much special in this, and I even added a little DisplayHelp() method.
The config file will look something like this:
<?xml version="1.0"?>
<configuration>
<configSections>
</configSections>
<connectionStrings>
<add name="SalesLogix_GetNextID.Properties.Settings.SLXConnString"
connectionString="Provider=SLXOLEDB.1;Password=adminpassword;Persist Security Info=True;User ID=admin;Initial Catalog=SalesLogixDB;Data Source=SLXServerName;OLE DB Services=-4;Extended Properties="PORT=1706;LOG=OFF;CASEINSENSITIVEFIND=ON;"" />
</connectionStrings>
<startup><supportedRuntime version="v2.0.50727"/></startup></configuration>
You will need to modify the connection string for your environment. Now this executable can be called very easily from sql using xp_cmdshell. Here is what we use:
/*****************************************************************************************************************
* Create Date: 07/20/2011
* Created By: Jason Van Pee
* Description: Gets the next IDs for SalesLogix tables.
* Paramaters:
* @TableName - the table to generate ids for
* @IDCount - the desired number of ids
*****************************************************************************************************************/
CREATE PROCEDURE [dbo].[usp_GetNextID]
@TableName sysname
, @IDCount INT
WITH EXECUTE AS 'XPCmdShellProxy'
AS
BEGIN
/* the location of the external exe that generates the ids */
DECLARE @ExeLocation AS VARCHAR(256) = 'c:\MSSQL\SalesLogix_GetNextID\SalesLogix_GetNextID.exe';
DECLARE @cmd AS VARCHAR(512);
SELECT @cmd = @ExeLocation + ' ' + DB_NAME() + ' ' + @TableName + ' ' + CONVERT(VARCHAR(10),@IDCount);
EXEC sys.xp_cmdshell @cmd;
END
GO
You may need to change where the executable is being stored and before creating this stored procedure in your environment you will need to set up the XPCmdShellProxy user. We are doing this so we do not have to give everybody using this stored procedure sysadmin rights to the server. This XPCmdShellProxy user will need to be added to your SalesLogix database and it will need to have execute access to xp_cmdshell in the master database. Here is a script you can use to create this user:
USE master
GO
CREATE LOGIN XPCmdShellProxy WITH PASSWORD = 'password', CHECK_POLICY = OFF;
GO
CREATE USER XPCmdShellProxy FOR LOGIN XPCmdShellProxy;
GO
GRANT EXECUTE ON sys.xp_cmdshell TO XPCmdShellProxy;
GO
USE SalesLogixDB
GO
CREATE USER XPCmdShellProxy FOR LOGIN XPCmdShellProxy;
GO
The password should be modified and you may want to turn CHECK_POLICY = ON. The last thing to consider is making sure you have a proxy account set up for xp_cmdshell. You can use the sp_xp_cmdshell_proxy_account stored procedure to set that. We created a local account with local admin rights for our proxy account. I would really like to limit the security for this account but it seems to fail on opening the connection if the account is not a local admin. If anybody knows how I can get around this, please let me know.