#
// Copyright (c) 2008, David DeWinter and Ryan Hauert
// All rights reserved.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
// OF SUCH DAMAGE.
#>
<#@ template language="C#" debug="True" #>
<#@ output extension=".sql" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ assembly name="Microsoft.SqlServer.ConnectionInfo, Version=9.0.242.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"#>
<#@ assembly name="Microsoft.SqlServer.Smo, Version=9.0.242.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data.EntityClient" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="Microsoft.SqlServer.Management.Smo" #>
<#
// Configuration Parameters:
// The full path of the directory where the .tt file is located.
string directoryName = "";
// By default this template will generate stored procedures for every EDMX file
// in the current directory. Use this file filter regular expression to constrain
// what EDMX files are parsed. For example, if you want to parse only a file
// with the name "Entities.edmx", then set edmxFileFilter = new Regex(@"Entities\.edmx");
Regex edmxFileFilter = null;
// Use this to create a prefix for your stored procedures.
string sprocPrefix = "";
#>
<#
// Conform the configuration parameters
if (String.IsNullOrEmpty(directoryName))
{
// The only way we can get the current directory is by looking at the stack trace. Note
// that the "debug" attribute of the template directive must be set to true for this to work.
string stackTraceFileName = new StackTrace(true).GetFrame(0).GetFileName();
if (String.IsNullOrEmpty(stackTraceFileName))
{
throw new ArgumentException("No value was specified for the 'directoryName' configuration parameter" +
", and we could not figure out the file name from the stack trace (most likely because of running " +
"the template with debug='False' specified in the <\u0023@ template \u0023> directive.");
}
else
{
directoryName = Path.GetDirectoryName(stackTraceFileName);
}
}
else if (!Directory.Exists(directoryName))
{
throw new ArgumentException("The specified directory does not exist: " + directoryName);
}
#>
--------------------------------------------------------------------------------
--
-- This code was generated by a tool.
--
-- Changes to this file may cause incorrect behavior and will be lost if
-- the code is regenerated.
--
--------------------------------------------------------------------------------
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
<#
string[] entityFrameworkFiles = Directory.GetFiles(directoryName, "*.edmx");
for (int i = 0; i < entityFrameworkFiles.Length; ++i)
{
string entityFrameworkFile = entityFrameworkFiles[i];
if (edmxFileFilter == null || edmxFileFilter.IsMatch(entityFrameworkFile))
{
#>
-- Creating stored procedures for <#= entityFrameworkFile #>.
<#
IEnumerable
tables = ExtractTables(entityFrameworkFile);
Dictionary tableLookup = new Dictionary();
bool tablesExist = false;
// Get the first table in the list so we can hook it up with a database.
foreach (Table table in tables)
{
if (!tablesExist)
{
tablesExist = true;
#>
USE "<#= table.Parent.Name #>"
GO
<#
}
tableLookup.Add(String.Format("{0}.{1}", table.Schema, table.Name), table);
}
if (!tablesExist)
{
continue;
}
StringBuilder insertParameterList = new StringBuilder();
StringBuilder insertColumnList = new StringBuilder();
StringBuilder insertValuesClause = new StringBuilder();
StringBuilder serverGeneratedColumns = new StringBuilder();
StringBuilder serverGeneratedColumnsWithDataTypes = new StringBuilder();
StringBuilder primaryKeyColumnRetrievalList = new StringBuilder();
StringBuilder updateParameterList = new StringBuilder();
StringBuilder updateColumnPairList = new StringBuilder();
StringBuilder updateColumnComparisonClause = new StringBuilder();
StringBuilder deleteParameterList = new StringBuilder();
StringBuilder deleteColumnComparisonClause = new StringBuilder();
// Loop over all the tables in the database of interest.
foreach (Table table in tables)
{
// Set up a lookup of the foreign keys in the table, to figure out which
// foreign key columns we care about for delete sprocs. (The Entity Framework
// requires there to be a mapping for all association columns, but the tables
// in the database might not reflect which tables are in the model.)
Dictionary foreignKeyLookup = new Dictionary();
ForeignKeyCollection foreignKeys = table.ForeignKeys;
int foreignKeyCount = foreignKeys.Count;
for (int j = 0; j < foreignKeyCount; ++j)
{
ForeignKey foreignKey = foreignKeys[j];
ForeignKeyColumnCollection foreignKeyColumns = foreignKey.Columns;
int foreignKeyColumnCount = foreignKeyColumns.Count;
if (tableLookup.ContainsKey(String.Format("{0}.{1}", foreignKey.ReferencedTableSchema, foreignKey.ReferencedTable)))
{
for (int k = 0; k < foreignKeyColumnCount; ++k)
{
foreignKeyLookup[foreignKeyColumns[k].Name] = null;
}
}
}
string partiallyEscapedQualifiedTableName = String.Format("{0}\".\"{1}{2}", table.Schema, sprocPrefix, table.Name);
string escapedQualifiedTableName = String.Format("\"{0}\".\"{1}\"", table.Schema, table.Name);
// Various tracking booleans to enable us to make optimizations
// in selecting server-generated columns when we write the INSERT sproc
bool hasIdentityPrimaryKey = false;
string identityColumn = null;
string identityPrimaryKeyColumn = null;
ColumnCollection columns = table.Columns;
int columnCount = columns.Count;
bool hasPrimaryKey = false;
bool hasUniqueIdentifierPrimaryKeyWithFunctionalDefault = false;
bool? hasOneServerGeneratedColumnThatIsIdentity = null;
// Iterate over all the columns before we start outputting the sproc contents.
// This way we iterate over the ColumnCollection only once.
for (int j = 0; j < columnCount; ++j)
{
Column column = columns[j];
string columnName = column.Name;
string dataType = ToSqlString(column.DataType);
// If the column is neither a computed column (regardless of whether
// it's a persisted computed column), an identity column, nor a primary
// key column, then it's a column we can update.
if (!column.Computed && !column.Identity && !column.InPrimaryKey)
{
AppendFormatWithSmallIndentAndNewLine(updateParameterList, "@{0} {1},", columnName, dataType);
AppendFormatWithIndentAndNewLine(updateColumnPairList, "\"{0}\" = @{0},", columnName);
}
bool uniqueIdentifierColumnHasFunctionalDefault = false;
if (dataType == "uniqueidentifier"
&& column.DefaultConstraint != null
&& Regex.IsMatch(column.DefaultConstraint.Text, @"^\(NEW(SEQUENTIAL)?ID\(\)\)$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture))
{
uniqueIdentifierColumnHasFunctionalDefault = true;
if (column.InPrimaryKey)
{
hasUniqueIdentifierPrimaryKeyWithFunctionalDefault = true;
}
}
if (column.Identity && !hasOneServerGeneratedColumnThatIsIdentity.HasValue)
{
hasOneServerGeneratedColumnThatIsIdentity = true;
identityColumn = "\"" + columnName + "\"";
}
if (column.InPrimaryKey)
{
hasPrimaryKey = true;
AppendFormatWithIndentAndNewLine(primaryKeyColumnRetrievalList, "\"{0}\" = @{0} AND", columnName);
if (column.Identity)
{
identityColumn = identityPrimaryKeyColumn = "\"" + columnName + "\"";
hasIdentityPrimaryKey = true;
}
if (!column.Computed)
{
// We'll use non-computed primary key columns to make
// comparisons in the WHERE clause in the UPDATE statement.
AppendFormatWithSmallIndentAndNewLine(updateParameterList, "@{0} {1},", columnName, dataType);
if (!column.Identity)
{
// We'll also put another parameter in the input list for the "original" ID.
// This original ID applies only when the primary key column is not an identity column.
string pascalCasedColumnName = UpperCaseFirst(columnName);
AppendFormatWithSmallIndentAndNewLine(updateParameterList, "@original{0} {1},", pascalCasedColumnName, dataType);
AppendFormatWithIndentAndNewLine(updateColumnPairList, "\"{0}\" = @{0},", columnName);
AppendFormatWithIndentAndNewLine(updateColumnComparisonClause, "\"{0}\" = @original{1} AND", columnName, pascalCasedColumnName);
}
else
{
// Otherwise, just compare on the identity column.
AppendFormatWithIndentAndNewLine(updateColumnComparisonClause, "\"{0}\" = @{0} AND", columnName);
}
}
// Both the delete column comparison clause and the parameter list ignore whether the PK is an identity or computed.
AppendFormatWithIndentAndNewLine(deleteColumnComparisonClause, "\"{0}\" = @{0} AND", columnName);
AppendFormatWithSmallIndentAndNewLine(deleteParameterList, "@{0} {1},", columnName, dataType);
}
else if (column.IsForeignKey && foreignKeyLookup.ContainsKey(columnName))
{
// If the column is a foreign key column, then the EF dictates that
// for the delete stored procedure to take in these parameters.
AppendFormatWithSmallIndentAndNewLine(deleteParameterList, "@{0} {1},", columnName, dataType);
}
// If the column is generated on the server
if (column.Computed || column.Identity || uniqueIdentifierColumnHasFunctionalDefault)
{
serverGeneratedColumns.AppendFormat(" INSERTED.\"{0}\",", column.Name);
AppendFormatWithIndentAndNewLine(serverGeneratedColumnsWithDataTypes, "{0} {1},", columnName, dataType);
if (!column.Identity)
{
hasOneServerGeneratedColumnThatIsIdentity = false;
}
}
else
{
if (!uniqueIdentifierColumnHasFunctionalDefault)
{
AppendFormatWithSmallIndentAndNewLine(insertParameterList, "@{0} {1},", columnName, dataType);
AppendFormatWithIndentAndNewLine(insertValuesClause, "@{0},", columnName);
AppendFormatWithIndentAndNewLine(insertColumnList, "\"{0}\",", columnName);
}
}
}
// Remove the trailing new line and any extra characters like commas or a superfluous "AND"
RemoveTrailingNewLine(updateColumnComparisonClause, 4);
RemoveTrailingNewLine(updateParameterList, 1);
RemoveTrailingNewLine(updateColumnPairList, 1);
RemoveTrailingNewLine(deleteColumnComparisonClause, 4);
RemoveTrailingNewLine(deleteParameterList, 1);
RemoveTrailingCharacters(serverGeneratedColumns, 1);
RemoveTrailingNewLine(serverGeneratedColumnsWithDataTypes, 1);
RemoveTrailingNewLine(insertParameterList, 1);
RemoveTrailingNewLine(insertValuesClause, 1);
RemoveTrailingNewLine(insertColumnList, 1);
RemoveTrailingNewLine(primaryKeyColumnRetrievalList, 4);
#>
------------------------------------------------------------------------------------------
--
-- <#= escapedQualifiedTableName #> Stored Procedures
--
------------------------------------------------------------------------------------------
<#
if (hasPrimaryKey)
{
#>
IF OBJECT_ID('"<#= partiallyEscapedQualifiedTableName #>_Insert"') IS NOT NULL
DROP PROC "<#= partiallyEscapedQualifiedTableName #>_Insert"
GO
CREATE PROCEDURE "<#= partiallyEscapedQualifiedTableName #>_Insert"
<# if (insertParameterList.Length > 0)
{
#>
(
<#= insertParameterList #>
)
<#
}
#>
AS
BEGIN
<#
if (hasUniqueIdentifierPrimaryKeyWithFunctionalDefault)
{
#>
DECLARE @results TABLE
(
<#= serverGeneratedColumnsWithDataTypes #>
)
<#
}
#>
INSERT INTO <#= escapedQualifiedTableName #>
<#
if (insertColumnList.Length > 0)
{
#> (
<#= insertColumnList #>
)
<#
}
if (hasUniqueIdentifierPrimaryKeyWithFunctionalDefault)
{
#>
OUTPUT <#= serverGeneratedColumns #> INTO @results
<#
}
if (insertValuesClause.Length > 0)
{
#>
VALUES
(
<#= insertValuesClause #>
)
<#
}
else
{
#>
DEFAULT VALUES
<#
}
if (hasUniqueIdentifierPrimaryKeyWithFunctionalDefault)
{
#>
SELECT * FROM @results
<#
}
else if (serverGeneratedColumns.Length > 0)
{
if (hasOneServerGeneratedColumnThatIsIdentity.HasValue && hasOneServerGeneratedColumnThatIsIdentity.Value)
{
#>
SELECT SCOPE_IDENTITY() AS <#= identityColumn #>
<#
}
else if (hasIdentityPrimaryKey)
{
#>
SELECT <#= serverGeneratedColumns #>
FROM <#= escapedQualifiedTableName #> AS INSERTED
WHERE <#= identityPrimaryKeyColumn #> = SCOPE_IDENTITY()
<#
}
else
{
#>
SELECT <#= serverGeneratedColumns #>
FROM <#= escapedQualifiedTableName #> AS INSERTED
WHERE
<#= primaryKeyColumnRetrievalList #>
<#
}
}
#>
END
GO
IF OBJECT_ID('"<#= partiallyEscapedQualifiedTableName #>_Update"') IS NOT NULL
DROP PROC "<#= partiallyEscapedQualifiedTableName #>_Update"
GO
CREATE PROCEDURE "<#= partiallyEscapedQualifiedTableName #>_Update"
(
<#= updateParameterList #>
)
AS
BEGIN
UPDATE <#= escapedQualifiedTableName #>
SET
<#= updateColumnPairList #>
WHERE
<#= updateColumnComparisonClause #>
END
GO
IF OBJECT_ID('"<#= partiallyEscapedQualifiedTableName #>_Delete"') IS NOT NULL
DROP PROC "<#= partiallyEscapedQualifiedTableName #>_Delete"
GO
CREATE PROCEDURE "<#= partiallyEscapedQualifiedTableName #>_Delete"
(
<#= deleteParameterList #>
)
AS
BEGIN
DELETE FROM <#= escapedQualifiedTableName #>
WHERE
<#= deleteColumnComparisonClause #>
END
GO
<#
}
else
{
#>
-- Errors Found During Generation:
-- The table <#= escapedQualifiedTableName #> does not have a primary key defined. No insert, update, or delete sproc can be created.
<#
}
// Clear the StringBuilders so we can use them for the next Table.
insertParameterList.Length = insertColumnList.Length = insertValuesClause.Length = serverGeneratedColumns.Length =
updateParameterList.Length = updateColumnComparisonClause.Length =
primaryKeyColumnRetrievalList.Length = serverGeneratedColumnsWithDataTypes.Length =
updateColumnPairList.Length = deleteParameterList.Length = deleteColumnComparisonClause.Length = 0;
}
}
}
#>
<#+
XNamespace edmxns = "http://schemas.microsoft.com/ado/2007/06/edmx";
XNamespace edmns = "http://schemas.microsoft.com/ado/2006/04/edm";
XNamespace ssdlns = "http://schemas.microsoft.com/ado/2006/04/edm/ssdl";
private IEnumerable ExtractTables(string edmxFilePath)
{
// Get the EntityContainer name from the edmx to find the
// correct connection string in the in the app.config.
XDocument edmxDoc = XDocument.Load(edmxFilePath);
XElement edmxElement = edmxDoc.Element(edmxns + "Edmx"); // element
XElement runtimeElement = edmxElement.Element(edmxns + "Runtime"); // element
XElement storageModelsElement = runtimeElement.Element(edmxns + "StorageModels"); // element
XElement ssdlSchemaElement = storageModelsElement.Element(ssdlns + "Schema");
string entityContainerName = runtimeElement
.Element(edmxns + "ConceptualModels") // element
.Element(edmns + "Schema") // element
.Element(edmns + "EntityContainer") // element
.Attribute("Name").Value;
string ssdlNamespace = ssdlSchemaElement.Attribute("Namespace").Value;
// Get the connection string out of the EF connection string from the app.config.
XDocument configFile = XDocument.Load(Path.Combine(Path.GetDirectoryName(edmxFilePath), "app.config"));
IEnumerable connectionStrings = configFile.Element("configuration") // element
.Element("connectionStrings") // element
.Elements("add");
XElement entityConnectionStringElement = Enumerable.FirstOrDefault(connectionStrings, delegate(XElement xe)
{
return xe.Attribute("name").Value == entityContainerName;
});
if (entityConnectionStringElement == null)
{
this.Write(String.Format("-- The {0} file does not have an associated connection string.{1}", edmxFilePath, Environment.NewLine));
yield break;
}
string entityConnectionString = entityConnectionStringElement.Attribute("connectionString").Value;
string connectionString = new EntityConnectionStringBuilder(entityConnectionString).ProviderConnectionString;
SqlConnectionStringBuilder connectionStringBuilder = new SqlConnectionStringBuilder(connectionString);
Server server = new Server(connectionStringBuilder.DataSource);
Database database = server.Databases[connectionStringBuilder.InitialCatalog];
// Get a list of tables from the SSDL.
XElement entityContainerElement = ssdlSchemaElement.Element(ssdlns + "EntityContainer");
IEnumerable entitySets = entityContainerElement.Elements(ssdlns + "EntitySet");
string defaultSchema = entityContainerElement.Attribute("Name").Value;
foreach (XElement entitySet in entitySets)
{
string tableName = entitySet.Attribute("Name").Value;
string schema = defaultSchema;
XAttribute schemaAttribute = entitySet.Attribute("Schema");
if (schemaAttribute != null && !String.IsNullOrEmpty(schemaAttribute.Value))
{
schema = schemaAttribute.Value;
}
Table table = database.Tables[tableName, schema];
if (table == null)
{
throw new InvalidOperationException(String.Format("Table \"{0}.{1}\" could not be found in target database.", schema, tableName));
}
yield return table;
}
}
private string ToSqlString(DataType dataType)
{
switch (dataType.Name)
{
case "char":
case "varchar":
case "nchar":
case "nvarchar":
case "binary":
case "varbinary":
return String.Format("{0}({1})", dataType.Name, dataType.MaximumLength);
case "decimal":
case "numeric":
return String.Format("{0}({1},{2})", dataType.Name, dataType.NumericPrecision, dataType.NumericScale);
default:
return dataType.Name;
}
}
private void RemoveTrailingCharacters(StringBuilder sb)
{
RemoveTrailingCharacters(sb, 1);
}
private void RemoveTrailingCharacters(StringBuilder sb, int characters)
{
if (sb.Length != 0)
{
sb.Remove(sb.Length - characters, characters);
}
}
private void RemoveTrailingNewLine(StringBuilder sb, int extraCharactersToRemove)
{
RemoveTrailingCharacters(sb, Environment.NewLine.Length + extraCharactersToRemove);
}
private string UpperCaseFirst(string value)
{
return Char.ToUpper(value.Substring(0, 1)[0]) + value.Substring(1, value.Length - 1);
}
private void AppendFormatWithNewLine(StringBuilder sb, string format, params object[] args)
{
sb.AppendFormat(format + Environment.NewLine, args);
}
private void AppendFormatWithIndent(StringBuilder sb, string format, params object[] args)
{
sb.AppendFormat("\t\t\t" + format, args);
}
private void AppendFormatWithIndentAndNewLine(StringBuilder sb, string format, params object[] args)
{
AppendFormatWithNewLine(sb, "\t\t\t" + format, args);
}
private void AppendFormatWithSmallIndentAndNewLine(StringBuilder sb, string format, params object[] args)
{
AppendFormatWithNewLine(sb, "\t" + format, args);
}
#>