I have an installer which installs a database. The database is created alongside some logins. To create the logins I am using the master database in SqlString elements. Access to the master database is only granted to users who have very high privileges on the SQL server. Oftentimes the installation is aborted because a SQL string designated for the master database cannot be executed due the lack of rights.
I want to edit my installer, so that when a SqlString element cannot be executed, the SQL part of the installation shall be skipped. After the installation has taken place I want the user to be able to execute the SQL statements herself. Every SQL action taken by my installer is stored in SqlString elements. The SqlString elements contain a lot of properties which get replaced during the installation. I want to extract the content of all edited SqlString elements into one sql file stored in the user directory.
I guess I'll have to write a customaction which takes place after the sqlextension has substituted the properties. And then I'll have to access these altered strings. Is there any way I can do this?
Example SqlString element:
<sql:SqlDatabase Id="MasterDB" Server="[SQLSERVER_SERVER]" Instance="[SQLSERVER_INSTANCENAME]" Database="master" />
<sql:SqlString
SqlDb="MasterDB"
Id="CreateNetworkServiceAccount"
ExecuteOnInstall="yes"
ContinueOnError="no"
SQL="IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'{[WIX_ACCOUNT_NETWORKSERVICE]}')
CREATE LOGIN [\[]{[WIX_ACCOUNT_NETWORKSERVICE]}[\]] FROM WINDOWS WITH DEFAULT_DATABASE=[\[]master[\]]"
Sequence="101"/>
Example of the sql file I'd like to have after the SqlStrings have failed:
USE master;
IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'NT AUTHORITY\Network Service')
CREATE LOGIN [NT AUTHORITY\Network Service] FROM WINDOWS WITH DEFAULT_DATABASE=[master]
I have solved this problem with a rather odd solution. I have written a CustomAction which extracts the String elements from the SqlString table and then replaces the formatted fields with the appropriate Properties stored in the session. To have access to the session variable, the CustomAction has to be executed as immediate. I've scheduled it before InstallFinalize to be given access to the PersonalFolder property. With this property I am able to store a Sql script generated by the entries in the SqlScript table in the users Documents directory. To account for different databases in the Installation, I have included a lookup in the SqlDatabase table.
Here is the code to the CustomAction:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using System.IO;
using System.Text.RegularExpressions;
namespace SaveSqlStrings
{
public class CustomActions
{
[CustomAction]
public static ActionResult SaveSqlStrings(Session session)
{
StringBuilder sqlStrings = new StringBuilder();
Database db = session.Database;
View view = db.OpenView("SELECT * FROM `SqlString`");
IList<string> SqlStringElements = db.ExecuteStringQuery("SELECT `String` FROM `SqlString`");
Regex bracketedProperties = new Regex(#"\[(\b[A-Z_]*\b)\]");
Regex formattedProperties = new Regex(#"{\[(\b[A-Z_]*\b)\]}");
Regex openeningSquareBrackets = new Regex(#"\[\\\[\]");
Regex closingSquareBrackets = new Regex(#"\[\\\]\]");
string sqlDb_ = "";
string sqlString = "";
string Database = "";
foreach (string dbString in SqlStringElements)
{
sqlDb_ = (string)db.ExecuteScalar("SELECT `SqlDb_` FROM `SqlString` WHERE `String` ='{0}'",dbString);
sqlString = (string)db.ExecuteScalar("SELECT `SQL` FROM `SqlString` WHERE `String` ='{0}'",dbString);
view.Close();
view = db.OpenView("SELECT * FROM `SqlDatabase`");
Database = (string)db.ExecuteScalar("SELECT `Database` from `SqlDatabase` WHERE `SqlDb`='{0}'", sqlDb_);
if(bracketedProperties.IsMatch(Database))
{
Database = bracketedProperties.Match(Database).Groups[1].Value;
Database = session[Database];
}
if (openeningSquareBrackets.IsMatch(sqlString))
sqlString = openeningSquareBrackets.Replace(sqlString, "[");
if (closingSquareBrackets.IsMatch(sqlString))
sqlString = closingSquareBrackets.Replace(sqlString, "]");
if(formattedProperties.IsMatch(sqlString))
{
string propertyName = formattedProperties.Match(sqlString).Groups[1].Value;
string propertyValue = session[propertyName];
sqlString = formattedProperties.Replace(sqlString, propertyValue);
}
sqlStrings.AppendLine(String.Format("use {0}",Database));
sqlStrings.AppendLine(sqlString);
}
string home = session["PersonalFolder"];
string sqlPath = string.Concat(home, #"Script.sql");
try
{
File.WriteAllText(sqlPath, sqlStrings.ToString());
}
catch (Exception ex)
{
session["FailedTowrite"] = sqlPath;
}
view.Close();
db.Close();
return ActionResult.Success;
}
}
}
Related
I have created a SSIS-project that retrieves weather information (JSON format) using a Web API in a script task. I've been following this tutorial :Weather data API which works great if you only want to retrieve weather information from a fixed set of coordinates.
My goal now is to use a table where I have stored some coordinates as variable input in the API URL parameters instead of having the coordinates already set in the URL https://api.met.no/weatherapi/locationforecast/2.0/compact.json?lat=55.596&lon=15
So what I have done so for
Created a Script task that gathers the weather information:
#region Help: Introduction to the script task
/* The Script Task allows you to perform virtually any operation that can be accomplished in
* a .Net application within the context of an Integration Services control flow.
*
* Expand the other regions which have "Help" prefixes for examples of specific ways to use
* Integration Services features within this script task. */
#endregion
#region Namespaces
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Net;
#endregion
namespace ST_6f60bececd8f4f94afaf758869590918
{
/// <summary>
/// ScriptMain is the entry point class of the script. Do not change the name, attributes,
/// or parent of this class.
/// </summary>
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
#region Help: Using Integration Services variables and parameters in a script
/* To use a variable in this script, first ensure that the variable has been added to
* either the list contained in the ReadOnlyVariables property or the list contained in
* the ReadWriteVariables property of this script task, according to whether or not your
* code needs to write to the variable. To add the variable, save this script, close this instance of
* Visual Studio, and update the ReadOnlyVariables and
* ReadWriteVariables properties in the Script Transformation Editor window.
* To use a parameter in this script, follow the same steps. Parameters are always read-only.
*
* Example of reading from a variable:
* DateTime startTime = (DateTime) Dts.Variables["System::StartTime"].Value;
*
* Example of writing to a variable:
* Dts.Variables["User::myStringVariable"].Value = "new value";
*
* Example of reading from a package parameter:
* int batchId = (int) Dts.Variables["$Package::batchId"].Value;
*
* Example of reading from a project parameter:
* int batchId = (int) Dts.Variables["$Project::batchId"].Value;
*
* Example of reading from a sensitive project parameter:
* int batchId = (int) Dts.Variables["$Project::batchId"].GetSensitiveValue();
* */
#endregion
#region Help: Firing Integration Services events from a script
/* This script task can fire events for logging purposes.
*
* Example of firing an error event:
* Dts.Events.FireError(18, "Process Values", "Bad value", "", 0);
*
* Example of firing an information event:
* Dts.Events.FireInformation(3, "Process Values", "Processing has started", "", 0, ref fireAgain)
*
* Example of firing a warning event:
* Dts.Events.FireWarning(14, "Process Values", "No values received for input", "", 0);
* */
#endregion
#region Help: Using Integration Services connection managers in a script
/* Some types of connection managers can be used in this script task. See the topic
* "Working with Connection Managers Programatically" for details.
*
* Example of using an ADO.Net connection manager:
* object rawConnection = Dts.Connections["Sales DB"].AcquireConnection(Dts.Transaction);
* SqlConnection myADONETConnection = (SqlConnection)rawConnection;
* //Use the connection in some code here, then release the connection
* Dts.Connections["Sales DB"].ReleaseConnection(rawConnection);
*
* Example of using a File connection manager
* object rawConnection = Dts.Connections["Prices.zip"].AcquireConnection(Dts.Transaction);
* string filePath = (string)rawConnection;
* //Use the connection in some code here, then release the connection
* Dts.Connections["Prices.zip"].ReleaseConnection(rawConnection);
* */
#endregion
/// <summary>
/// This method is called when this script task executes in the control flow.
/// Before returning from this method, set the value of Dts.TaskResult to indicate success or failure.
/// To open Help, press F1.
/// </summary>
public void Main()
{
string Longitude = (string)Dts.Variables["User::Longitude"].Value.ToString();
string Latitude = (string)Dts.Variables["User::Latitude"].Value.ToString();
var url = #"https://api.met.no/weatherapi/locationforecast/2.0/compact.json?lat=55.596&lon=15";
System.Net.ServicePointManager.DefaultConnectionLimit = int.MaxValue;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.UseDefaultCredentials = true;
req.Proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
var syncClient = new WebClient();
syncClient.Headers.Add("user-agent", "acmeweathersite.com support#acmeweathersite.com");
var content = syncClient.DownloadString(url);
string connectionString = "Data Source=localhost;Initial Catalog=Weather;Integrated Security=True;";
using (SqlConnection conn = new SqlConnection(connectionString))
{
SqlCommand Storproc =
new SqlCommand(#"INSERT INTO [dbo].[Weather] (JSONData)
select #JSONData", conn);
Storproc.Parameters.AddWithValue("#JSONData", content.ToString());
conn.Open();
Storproc.ExecuteNonQuery();
conn.Close();
}
// TODO: Add your code here
Dts.TaskResult = (int)ScriptResults.Success;
}
#region ScriptResults declaration
/// <summary>
/// This enum provides a convenient shorthand within the scope of this class for setting the
/// result of the script.
///
/// This code was generated automatically.
/// </summary>
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
}
}
Created a SQL table with coordinates:
create table Coordinates(
Municipality nvarchar(50),
Latitide nvarchar(50),
Longitude nvarchar(50)
)
INSERT INTO Coordinates (Municipality, Latitide, Longitude)
VALUES (114, 59.5166667, 17.9),
(115, 59.5833333, 18.2),
(117, 59.5, 18.45)
Added the coordinates table as an SQL task:
Finally I added the two variables in the Script task code:
string Longitude = (string)Dts.Variables["User::Longitude"].Value.ToString();
string Latitude = (string)Dts.Variables["User::Latitude"].Value.ToString();
var url = #"https://api.met.no/weatherapi/locationforecast/2.0/compact.json?lat=Latitude&lon=Longitude";
But when I execute the package the Foreach Loop Container just loops forever, no errors pops up but no data is being stored in the database table either. It feels like I have missed something but no really sure what. Very novice when it comes to variables in SSIS so excuse my lack of knowledge. in my example I am using a SQL Server 2019.
Looks like you are nearly there.
Few things to check:
In your SQL, you are selecting 3 columns, so, the 'index' for these columns would be:
Municipality -> index = 0
Latitide -> index = 1
Longitude -> index = 2
i.e., in the variable mapping, you need to use index 1 and 2 instead of zero for all.
https://learn.microsoft.com/en-us/sql/integration-services/control-flow/foreach-loop-container?view=sql-server-ver15
The first column defined in the enumerator item has the index value 0, the second column 1, and so on.
Your spelling for the columns also seems different (Latitide Vs Latitude). Cross-check this as well. i.e., if you run your sql statement manually are you able to see the data ? What are the column names for the result ?
You can also check the variables in your script task (for debugging purposes) by adding a MessageBox.
E.g.,
string longitude = (string)Dts.Variables["User::Longitude"].Value.ToString();
string latitide = (string)Dts.Variables["User::Latitide"].Value.ToString();
string municipality = (string)Dts.Variables["User::Municipality"].Value.ToString();
MessageBox.Show("longitude:" + longitude + ", latitide:" + latitide + ", municipality: " + municipality);
Parsing an SSIS package for documentation purposes and I want to wildcard the trap for script code in case the creator did not leave the name as "ScriptMain"
Here is the code section I have right now:
SELECT RowID as ControlFlowRowID,
CF.TaskName
,cfnodes1.x.value('./ProjectItem[#Name=''ScriptMain.cs''] [1]', 'nvarchar(max)') CSScript
,cfnodes1.x.value('./ProjectItem[#Name=''ScriptMain.vb''][1]', 'nvarchar(max)') VBScript
FROM ##tmp_SSISpkgControlFlow cf
CROSS APPLY Cf.ScriptTaskQry.nodes('.') AS cfnodes1(x)
I want to take out the dependence on ScriptMain. Any help would be great.
section of XML in question:
</ProjectItem>
<ProjectItem
Name="ScriptMain.cs"
Encoding="UTF8"><![CDATA[#region Help: Introduction to the script task
/* The Script Task allows you to perform virtually any operation that can be accomplished in
* a .Net application within the context of an Integration Services control flow.
*
* Expand the other regions which have "Help" prefixes for examples of specific ways to use
* Integration Services features within this script task. */
#endregion
#region Namespaces
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
using System.Data.OleDb;
using System.Data.SqlClient;
using System.Xml;
using System.IO;
#endregion
namespace ST_2817a78c2b684bfc87bfd7fb00086a37
{
/// <summary>
/// ScriptMain is the entry point class of the script. Do not change the name, attributes,
/// or parent of this class.
/// </summary>
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
#region Help: Using Integration Services variables and parameters in a script
/* To use a variable in this script, first ensure that the variable has been added to
* either the list contained in the ReadOnlyVariables property or the list contained in
* the ReadWriteVariables property of this script task, according to whether or not your
* code needs to write to the variable. To add the variable, save this script, close this instance of
* Visual Studio, and update the ReadOnlyVariables and
* ReadWriteVariables properties in the Script Transformation Editor window.
* To use a parameter in this script, follow the same steps. Parameters are always read-only.
*
* Example of reading from a variable:
* DateTime startTime = (DateTime) Dts.Variables["System::StartTime"].Value;
*
* Example of writing to a variable:
* Dts.Variables["User::myStringVariable"].Value = "new value";
*
* Example of reading from a package parameter:
* int batchId = (int) Dts.Variables["$Package::batchId"].Value;
*
* Example of reading from a project parameter:
* int batchId = (int) Dts.Variables["$Project::batchId"].Value;
*
* Example of reading from a sensitive project parameter:
* int batchId = (int) Dts.Variables["$Project::batchId"].GetSensitiveValue();
* */
#endregion
#region Help: Firing Integration Services events from a script
/* This script task can fire events for logging purposes.
*
* Example of firing an error event:
* Dts.Events.FireError(18, "Process Values", "Bad value", "", 0);
*
* Example of firing an information event:
* Dts.Events.FireInformation(3, "Process Values", "Processing has started", "", 0, ref fireAgain)
*
* Example of firing a warning event:
* Dts.Events.FireWarning(14, "Process Values", "No values received for input", "", 0);
* */
#endregion
#region Help: Using Integration Services connection managers in a script
/* Some types of connection managers can be used in this script task. See the topic
* "Working with Connection Managers Programatically" for details.
*
* Example of using an ADO.Net connection manager:
* object rawConnection = Dts.Connections["Sales DB"].AcquireConnection(Dts.Transaction);
* SqlConnection myADONETConnection = (SqlConnection)rawConnection;
* //Use the connection in some code here, then release the connection
* Dts.Connections["Sales DB"].ReleaseConnection(rawConnection);
*
* Example of using a File connection manager
* object rawConnection = Dts.Connections["Prices.zip"].AcquireConnection(Dts.Transaction);
* string filePath = (string)rawConnection;
* //Use the connection in some code here, then release the connection
* Dts.Connections["Prices.zip"].ReleaseConnection(rawConnection);
* */
#endregion
/// <summary>
/// This method is called when this script task executes in the control flow.
/// Before returning from this method, set the value of Dts.TaskResult to indicate success or failure.
/// To open Help, press F1.
/// </summary>
public void Main()
{
string connectionString = "";
string filepath = Dts.Variables["varSourceFilePath"].Value.ToString();
string serverName = Dts.Variables["varServerName"].Value.ToString();
string databaseName = Dts.Variables["varDBName"].Value.ToString();
connectionString = #"Data Source=" + serverName + ";Initial Catalog=" + databaseName + ";Integrated Security=true;";
StreamReader reader = File.OpenText(filepath);
string input = null;
string abc = null;
string Col;
while ((input = reader.ReadLine()) != null)
{
abc = abc + input;
}
Col = abc.ToString();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string queryString = "insert into [pkgStats]([PackageXML]) Values(#field1)";
SqlCommand command = new SqlCommand(queryString, connection);
command.Parameters.AddWithValue("#field1", Col.ToString());
command.ExecuteReader();
connection.Close();
}
Dts.TaskResult = (int)ScriptResults.Success;
}
#region ScriptResults declaration
/// <summary>
/// This enum provides a convenient shorthand within the scope of this class for setting the
/// result of the script.
///
/// This code was generated automatically.
/// </summary>
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
}
}]]></ProjectItem>
<ProjectItem
Name="ST_2817a78c2b684bfc87bfd7fb00086a37.csproj"
Encoding="UTF8"><![CDATA[<?xml version="1.0" encoding="utf-8"?>
UPDATE: Here is the revised SQL with the Accepted Answer applied:
select
Data.ControlFlowRowID as CF_Rowid,
ScriptName,
ScriptCode as ScriptText,
ScriptType
from
(
SELECT RowID as ControlFlowRowID,
CF.TaskName,
CASE WHEN ProjectItem.value('#Name','varchar(max)') LIKE '%.vb' THEN 'VB Script'
WHEN ProjectItem.value('#Name','varchar(max)') LIKE '%.cs' THEN 'C# SCript'
ELSE 'Other'
END AS ScriptType
,ProjectItem.value('#Name','varchar(max)') AS ScriptName
,ProjectItem.value('.','varchar(max)') AS ScriptCode
FROM ##tmp_SSISpkgControlFlow cf
CROSS APPLY Cf.ScriptTaskQry.nodes('//ProjectItem') AS A(ProjectItem)
) as Data
where Data.ScriptCode is not null and ScriptType <> 'Other'
Sorry for the delay...
Is it something like this you are looking for?
(just paste it into an empty query window and adapt to your needs)
DECLARE #xml XML=
'<Root>
<ProjectItem Name="ScriptMain.cs" Encoding="UTF8">
<![CDATA[Here you find CS code]]>
</ProjectItem>
<ProjectItem Name="ScriptMain.vb" Encoding="UTF8">
<![CDATA[Here you find Vb code]]>
</ProjectItem>
<ProjectItem Name="OtherName.cs" Encoding="UTF8">
<![CDATA[Here you find CS code with other name]]>
</ProjectItem>
<ProjectItem Name="OtherName.vb" Encoding="UTF8">
<![CDATA[Here you find VB code with other name]]>
</ProjectItem>
<ProjectItem Name="OtherName.xy" Encoding="UTF8">
<![CDATA[Here you find XY code with other name]]>
</ProjectItem>
</Root>
'
;
select CASE WHEN ProjectItem.value('#Name','varchar(max)') LIKE '%.vb' THEN 'VB'
WHEN ProjectItem.value('#Name','varchar(max)') LIKE '%.cs' THEN 'CS'
ELSE 'Other'
END AS ScriptType
,ProjectItem.value('#Name','varchar(max)') AS ScriptName
,ProjectItem.value('.','varchar(max)') AS ScriptCode
FROM #xml.nodes('//ProjectItem') AS A(ProjectItem)
The Result is
ScriptType ScriptName ScriptCode
CS ScriptMain.cs Here you find CS code
VB ScriptMain.vb Here you find Vb code
CS OtherName.cs Here you find CS code with other name
VB OtherName.vb Here you find VB code with other name
Other OtherName.xy Here you find XY code with other name
I have an sql server database table which has xml column name called "MESSAGE" and which will store xml data.
The database table look like,
Now I need to get this "MESSAGE" column data and save into System physical path as xml file(Ex: test.xml etc.,)
Any suggestion how to implement this using c#.net?
You could try something like this (using plain ADO.NET and a very basic SQL query):
static void Main(string[] args)
{
// get connection string from app./web.config
string connectionString = "server=.;database=yourDB;Integrated Security=SSPI;";
// define query
string query = "SELECT MESSAGE FROM dbo.SamTest WHERE ID = 1;";
// set up connection and command
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand selectCmd = new SqlCommand(query, conn))
{
// open connection, execute query to get XML, close connection
conn.Open();
string xmlContents = selectCmd.ExecuteScalar().ToString();
conn.Close();
// define target file name
string targetFileName = #"C:\tmp\test.xml";
// write XML out to file
File.WriteAllText(targetFileName, xmlContents);
}
}
This question is related to another one I posted earlier.
To recap, I need to fix an issue with an ancient legacy app where people messed up data storage by re-installing the software the wrong way.
The application stores data by saving a record in an SQL DB. Each record holds a reference to a file on disk of which the filename auto-increments.
By re-installing the app the filename auto-increment was re-set so the DB now holds multiple unrelated records which reference the same filename and I have to directories with files which I obviously cannot merge because of these identical filenames. The files hold no reference to the DB data so the only course of action that remains is to filter the DB records on date created and try to rename "EXED" to "IXED" or something like that.
The DB is relatively simple with one table containing a column that holds data of type "Image".
An example content of this image data is as follows:
0x3200001000000000000000200B0000000EFF00000300000031340000000070EC0100002C50000004000000C90000005D010000040000007955B63F4D01000004000000F879883E4F01000004000000BC95563E98010000040000009A99993F4A01000004000000000000004B01000004000000000000009101000004000000000000004E01000004000000721C83425101000004000000D841493F5E01000004000000898828414101000004000000F2D2BD3F4201000004000000FCA9B13F40010000040000007574204244010000040000000000204345010000040000007DD950414601000004000000000000004701000004000000000000009201000004000000000000008701000004000000D2DF13426A0100000400000000005C42740100000400000046B68F40500100000400000018E97A3F7901000004000000FB50CF3C7A01000004000000E645703F99010000040000000000E0404C010000040000008716593F8601000004000000000006439A0100000400000000008040700100000400000063D887449E01000004000000493CBA3E9C0100000400000069699D429B01000004000000DD60CA3F9D0100000400000035DE3C44B4010000040000008B5C744433000000040000003D0ABB4134000000040000000AFF7C44350000000400000093CB3942750400000400000054A69F41BA010000040000002635C64173040000040000008367C24100000080690100002B5000003101000032000010000000000000002009000000000000000100000000000000F00000000000000080080100000100000010000000540100000100000021F0AA42270000000200000010000000540100000200000021F0AA42280000000300000010000000540100000300000059C9E6432900000004000000100000005401000004000000637888442A00000005000000100000005401000005000000DFEF87442B00000006000000100000005401000006000000000000002C00000007000000100000005401000007000000000000002D00000008000000100000005401000008000000000000002D000000090000001000000054010000090000002F353D442D0000000A00000010000000540100000A00000035DE3C44340000000B00000010000000540100000B0000008B5C7444240000009D50000010000000CDCCCC3E2C513B41F65D5F3F2C51BB419E50000010000000CCBA2C3FE17C8C411553B13F83F32142000000403700000000FE0000090000004558454434386262002D50000008000000447973706E6F65008E5000000E00000056454C442052414D502033363000000000F000000000
The data is apparently Hex which mostly encodes meaningless crap but also holds the name of physical files (towards the end of the data field) in the filesystem that is linked to the SQL records:
??#7???????????EXED48bb?-P??????Dyspnoe??P??????VELD RAMP 360
I'm interested in the EXED part.
There is no clear regularity in the offset at which the filename appears and the filename is of variable length (so I do not know beforehand how long the substring will be).
I can call up all records with SQL like this:
SELECT COUNT(*) as "Number of EXED Files after critical date"
FROM [ZAN].[dbo].[zanu]
WHERE udata is not null
and SUBSTRING(udata, 1 , 2147483647) like '%EXED%'
and [udatum] > 0
and CONVERT(date,[udatum]) > CONVERT(date,'20100629')
What I would like to do now is know how to replace this EXED substring by something else (e.g. IXID).
I'm unfamiliar with SQL and Googling so far has yielded very little information on my options here.
I also have no other info on the original code that generated this data/the data format/encoding/whatever...
It's a mess really.
Any help is welcome!
An update on this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Threading;
namespace ZANLinq
{
class Program
{
static void Main(string[] args)
{
try
{
DataContext zanDB = new DataContext(#"Data Source=.\,1433;database=ZAN;Integrated Security=true");
string strSQL = #"SELECT
Idnr,
Udatum,
Uzeit,
Unr,
Uart,
Ubediener,
Uzugriff,
Ugr,
Uflags,
Usize,
Udata
FROM Zanu
WHERE (Udata IS NOT null and SubString(Udata, 1 , 2147483647) LIKE '%EXED%')
AND (Idnr = ' 2')";
var zanQuery = zanDB.ExecuteQuery<Zanu>(strSQL);
List<Zanu> list = zanQuery.ToList<Zanu>();
foreach (Zanu zanTofix in list)
{
string strOriginal = ASCIIEncoding.ASCII.GetString(zanTofix.Udata);
string strFixed = strOriginal.Replace("EXED", "IXED");
zanTofix.Udata = ASCIIEncoding.ASCII.GetBytes(strFixed);
}
zanDB.SubmitChanges();
//Console.WriteLine(zanResults.Count<Zanu>().ToString());
}
catch (SqlException e)
{
Console.WriteLine(e.Message);
}
}
}
}
It finds the records I'm interested in, I can easily manipulate the data but the commit doesnt work. I'm stumped, there are no exceptions, no indication the code is wrong.
Anybody have ideas?
UPDATE:
I think the above does not work because my table appears to have a composite PK (I cannot change this):
Since I could not debug this (no info anywhere, no exceptions, just a silent fail of the submitchanges()) I decided to use another approach and abandon Linq2SQL altogether:
try
{
SqlConnection thisConnection = new SqlConnection(#"Network Library=DBMSSOCN;Data Source=.\,1433;database=ZAN;Integrated Security=SSPI");
DataSet zanDataSet = new DataSet();
SqlDataAdapter zanDa;
SqlCommandBuilder zanCmdBuilder;
thisConnection.Open();
//Initialize the SqlDataAdapter object by specifying a Select command
//that retrieves data from the sample table.
zanDa = new SqlDataAdapter(#"SELECT
Idnr,
Udatum,
Uzeit,
Unr,
Uart,
Ubediener,
Uzugriff,
Ugr,
Uflags,
Usize,
Udata
FROM Zanu
WHERE (Udata IS NOT null and SubString(Udata, 1 , 2147483647) LIKE '%IXED%')
AND (Idnr = ' 2')
AND (Uzeit = '13:21')", thisConnection);
//Initialize the SqlCommandBuilder object to automatically generate and initialize
//the UpdateCommand, InsertCommand, and DeleteCommand properties of the SqlDataAdapter.
zanCmdBuilder = new SqlCommandBuilder(zanDa);
//Populate the DataSet by running the Fill method of the SqlDataAdapter.
zanDa.Fill(zanDataSet, "Zanu");
Console.WriteLine("Records that will be affected: " + zanDataSet.Tables["Zanu"].Rows.Count.ToString());
foreach (DataRow record in zanDataSet.Tables["Zanu"].Rows)
{
string strOriginal = ASCIIEncoding.ASCII.GetString((byte[])record["Udata"]);
string strFixed = strOriginal.Replace("IXED", "EXED");
record["Udata"] = ASCIIEncoding.ASCII.GetBytes(strFixed);
//string strPostMod = ASCIIEncoding.ASCII.GetString((byte[])record["Udata"]);
}
zanDa.Update(zanDataSet, "Zanu");
thisConnection.Close();
Console.ReadLine();
}
catch (SqlException e)
{
Console.WriteLine(e.Message);
}
This seems to work but any input on why the Linq does not work and whether or not my second solution is efficient/optimal or not is still very much appreciated.
Sorry for my English first of all. I have a problem and need help.
I have a simple tool made by myself on c#. This tool makes connect to local or remote firebird server (v.2.5). And my tool can create specified .fdb file (database) somewhere on the server.
Also I have a file with SQL statements (create table, triggers and so on). I want to execute this file after database was created. Executing this file will fill structure of user database - not data, only structure.
But then I try to execute my SQL script - firebird server returns a
SQL error code = -104 Token unknown line xxx column xxx.
That's the line on this CREATE TABLE SQL statement, for example:
CREATE TABLE tb1
(
col1 INTEGER NOT NULL,
col2 VARCHAR(36)
);
/* This next create statement causes an error */
CREATE TABLE tb2
(
col1 INTEGER NOT NULL,
col2 VARCHAR(36)
);
If I will leave only one create statement in my file - all will be good... I don't know how I explained (it's clear or not)) - another words - why can't I execute full query with many create statements in one transaction? There is my main method which executes query:
public static string Do(string conString, string query)
{
using (FbConnection conn = new FbConnection())
{
try
{
conn.ConnectionString = conString;
conn.Open();
FbTransaction trans = conn.BeginTransaction();
FbCommand cmd = new FbCommand(query, conn, trans);
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.ToString());
return "Transaction Fail";
}
}
return "Transaction Commited";
}
There is a query is my SQL file.
As Victor already stated in his final comment, you can use the FBScript class for batch execution.
I was just confronted with the same task. This question pointed me in the right direction but i had to do some further digging.
I this example, the source of the statements is a external script file:
private void ExecuteScript(FbConnection myConnection, string scriptPath) {
if (!File.Exists(scriptPath))
throw new FileNotFoundException("Script not found", scriptPath);
FileInfo file = new FileInfo(scriptPath);
string script = file.OpenText().ReadToEnd();
// use FbScript to parse all statements
FbScript fbs = new FbScript(script);
fbs.Parse();
// execute all statements
FbBatchExecution fbe = new FbBatchExecution(myConnection, fbs);
fbe.Execute(true);
}
This will work fine, but you may wonder why this whole thing isn't surrounded by a transaction. Actually there is no support to "bind" FbBatchExecution to a transaction directly.
The first thing i tried was this (will not work)
private void ExecuteScript(FbConnection myConnection, string scriptPath) {
using (FbTransaction myTransaction = myConnection.BeginTransaction()) {
if (!File.Exists(scriptPath))
throw new FileNotFoundException("Script not found", scriptPath);
FileInfo file = new FileInfo(scriptPath);
string script = file.OpenText().ReadToEnd();
// use FbScript to parse all statements
FbScript fbs = new FbScript(script);
fbs.Parse();
// execute all statements
FbBatchExecution fbe = new FbBatchExecution(myConnection, fbs);
fbe.Execute(true);
myTransaction.Commit();
}
}
This will result in an exception stating: "Execute requires the Command object to have a Transaction object when the Connection object assigned to the command is in a pending local transaction. The Transaction property of the Command has not been initialized."
This means nothing more than that the commands that are executed by FbBatchExecution are not assigned to our local transaction that is surrounding the code block. What helps here is that that FbBatchExecution provides
the event CommandExecuting where we can intercept every command and assign our local transaction like this:
private void ExecuteScript(FbConnection myConnection, string scriptPath) {
using (FbTransaction myTransaction = myConnection.BeginTransaction()) {
if (!File.Exists(scriptPath))
throw new FileNotFoundException("Script not found", scriptPath);
FileInfo file = new FileInfo(scriptPath);
string script = file.OpenText().ReadToEnd();
// use FbScript to parse all statements
FbScript fbs = new FbScript(script);
fbs.Parse();
// execute all statements
FbBatchExecution fbe = new FbBatchExecution(myConnection, fbs);
fbe.CommandExecuting += delegate(object sender, CommandExecutingEventArgs args) {
args.SqlCommand.Transaction = myTransaction;
};
fbe.Execute(true);
// myTransaction.Commit();
}
}
Note that i have uncommented the myTransaction.Commit() line. I was a little bit surprised by this behavior, but if you keep that line the transaction will throw an exception stating that it has already been committed. The bool parameter fbe.Execute(true) is named "autoCommit", but changing this to false seems to have no effect.
I would like some feedback if you see any potential issues with assigning the local transaction this way, or if it has any benefits at all or could as well be omitted.
Probably error in launching two create statements in one batch. Would it work if you break it to separate queries? Does it work in your SQL tool?