Feeds:
Posts
Comments

Archive for the ‘SQL’ Category

POCO Object Creation

Updated!
And again! I just can’t leave well enough alone, can’t I?

I really like the Microsoft’s MVC and Entity Frameworks, and their use of Plan Ol’ C Objects (often just called POCO objects, and yes, I know that ‘objects’ is completely redundant) for data interactions. They’re simple to code, perform pretty well and most importantly, are easy to understand. However, I always run into a problem when starting a new project. Personally, I’m a database first kind of guy. I create the database, develop the initial schema and once that’s settled, I’m ready to start the code. This is completely the opposite of the Code First tooling in Visual Studio. If you do the Code First, you only create one set of objects in code and the tooling creates the database for you. But, in my case, I’ve already created the database – so what to do?

Create your own POCO generator is what. This is a pretty simple-minded POCO generator. It takes in a table name as a parameter and it’s output is a valid C# object. That said, it does cover a lot of possibilities, trying to generate as thorough of an object as possible.

drop procedure generatePOCO
go

create procedure generatePOCO @tablename sysname
as
begin
	-- Class declaration
	select
		'[Table("' +  @tablename + '")]' + char(13)
		+ 'public class ' + @tablename + char(13)
		+ '{'

	-- create a property with annotations for each column
	union all
	select
		-- key attribute
		case
			when 'ID' = UPPER(columns.name) or 
			(
				'_ID' =  RIGHT(UPPER(columns.name), 3) and 
				not exists (select 1 from sys.all_columns where object_id(@tablename) = all_columns.object_id and UPPER(name) = 'ID')
			) 
			then char(9) + '[Key]' + char(13)
			else ''
		end +

		-- hide "id" type fields using hidden attribute
		case
			when 'ID' = UPPER(columns.name) or '_ID' =  RIGHT(UPPER(columns.name), 3)  
			then char(9) + '[HiddenInput(DisplayValue=false)]' + char(13)
			else ''
		end +

		-- identity delcaration
		case
			when is_identity=1 then char(9) + '[DatabaseGenerated(DatabaseGeneratedOption.Identity)]' + char(13)
			else ''
		end +

		-- maxlength attribute
		case 
			when t.name like '%char%' and -1 <> max_length then char(9) + '[MaxLength(' + cast(max_length as varchar) + ')]'+char(13)
			else '' 
		end +
	
		-- default value attribute
		case
			when definition is not null then char(9) + '[DefaultValue' + REPLACE(definition, '''', '"') + ']' + char(13)
			else '' 
		end +
	
		-- Required for not nullable
		case
			when 0 = is_nullable then char(9) + '[Required]' + char(13)
			else ''
		end +

		-- No special characters
		case 
			when t.name like '%char%' and -1 <> max_length then char(9) + '[NoSpecialCharacters]'+char(13)
			else '' 
		end +
	
		-- property declaration
		REPLACE(
			char(9) + 'public '+
			case
				when t.name = 'bigint' then 'Int64'
				when t.name = 'bit' then 'Boolean'
				when t.name = 'date' then 'DateTime'
				when t.name = 'datetime' then 'DateTime'
				when t.name = 'datetime2' then 'DateTime'
				when t.name = 'datetimeoffset' then 'Datetimeoffset'
				when t.name = 'decimal' then 'Decimal'
				when t.name = 'float' then 'Double'
				when t.name = 'int' then 'Int32'
				when t.name = 'money' then 'Decimal'
				when t.name = 'numeric' then 'Decimal'
				when t.name = 'real' then 'Single'
				when t.name = 'smalldatetime' then 'DateTime'
				when t.name = 'smallint' then 'Int16'
				when t.name = 'smallmoney' then 'Decimal'
				when t.name = 'sql_variant' then 'Object*'
				when t.name = 'time' then 'TimeSpan'
				when t.name = 'tinyint' then 'Byte'
				when t.name = 'uniqueidentifier' then 'Guid'
				when t.name = 'xml' then 'Xml'

				when t.name like '%char%' then 'String'
				when t.name like '%text%' then 'String'
				else 'byte[]'
			end +
			case when is_nullable = 1 then '?' else '' end +
			' '+
			columns.name+
			' { get; set; }',
		'String?','String') -- replace is done since String doesn't need a '?' to allow nulls

		-- terminal <cr>
		+ char(13)
	from sys.all_objects tables
	inner join sys.all_columns columns on columns.object_id = tables.object_id
	inner join sys.systypes t on t.xusertype = columns.system_type_id
	left outer join sys.default_constraints dc on dc.name = OBJECT_NAME(default_object_id)
	where tables.name = @tablename

	-- commentary
	union all
	select char(9) + '// Foreign keys from this table to a master table' + char(13)

	-- foreign keys referenced from this table to another
	union all
	select 
		-- required attribute as this is the dependant key
		char(9) + '[Required]' + char(13) + 
		
		-- foreign key column declaration
		char(9) + '[ForeignKey("' + all_columns.name + '")]' + char(13) +

		-- foreign key declaration
		char(9) + 'public virtual ' + OBJECT_NAME(referenced_object_id) + ' ' + OBJECT_NAME(referenced_object_id) + ' { get; set; }'
	from sys.foreign_key_columns
	inner join sys.all_columns on all_columns.object_id = parent_object_id and all_columns.column_id = parent_column_id
	where OBJECT_ID(@tablename) = parent_object_id

	-- commentary 
	union all
	select char(9) + '// Foreign keys to this table from other tables' + char(13)
	
	-- foreign keys to this table from other tables
	union all
	select char(9) + 'public virtual ICollection<' + OBJECT_NAME(parent_object_id) + '> ' + OBJECT_NAME(parent_object_id) + ' { get; set; }'
	from sys.foreign_key_columns
	where OBJECT_ID(@tablename) = referenced_object_id

	-- close out class
	union all
	select '}' + char(13)
end
go

The output will look something like:

[Table("UserProfile")]
public class UserProfile
{
	[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
	[Required]
	public Int32 UserId { get; set; }

	[MaxLength(112)]
	[Required]
	[NoSpecialCharacters]
	public String UserName { get; set; }

	[MaxLength(500)]
	[Required]
	[NoSpecialCharacters]
	public String BusinessEmail { get; set; }

	[MaxLength(100)]
	[Required]
	[NoSpecialCharacters]
	public String FullName { get; set; }

	[MaxLength(100)]
	[Required]
	[NoSpecialCharacters]
	public String BusinessName { get; set; }

	[MaxLength(100)]
	[Required]
	[NoSpecialCharacters]
	public String BusinessAddress1 { get; set; }

	[MaxLength(100)]
	[NoSpecialCharacters]
	public String BusinessAddress2 { get; set; }

	[MaxLength(100)]
	[NoSpecialCharacters]
	public String BusinessAddress3 { get; set; }

	[MaxLength(100)]
	[Required]
	[NoSpecialCharacters]
	public String BusinessCity { get; set; }

	[MaxLength(4)]
	[Required]
	[NoSpecialCharacters]
	public String BusinessState { get; set; }

	[MaxLength(20)]
	[Required]
	[NoSpecialCharacters]
	public String BusinessZip { get; set; }

	[MaxLength(100)]
	[Required]
	[NoSpecialCharacters]
	public String BusinessPhone { get; set; }

	[MaxLength(100)]
	[NoSpecialCharacters]
	public String BusinessFax { get; set; }

	[MaxLength(100)]
	[Required]
	[NoSpecialCharacters]
	public String PasswordQuestion { get; set; }

	[MaxLength(100)]
	[Required]
	[NoSpecialCharacters]
	public String PasswordAnswer { get; set; }

	// Foreign keys from this table to a master table

	// Foreign keys to this table from other tables

	public virtual ICollection<webpages_UsersInRoles> webpages_UsersInRoles { get; set; }
}

for a parent table and for a child table:

[Table("webpages_UsersInRoles")]
public class webpages_UsersInRoles
{
	[Required]
	public Int32 UserId { get; set; }

	[Required]
	public Int32 RoleId { get; set; }

	// Foreign keys from this table to a master table

	[Required]
	[ForeignKey("UserId")]
	public virtual UserProfile UserProfile { get; set; }
	[Required]
	[ForeignKey("RoleId")]
	public virtual webpages_Roles webpages_Roles { get; set; }
	// Foreign keys to this table from other tables

}

The generator code examines the column definitions in Sql Server and uses the information to create the POCO object. It will check for non-null and add [Required] attributes, it will add [DefaultValue] attributes where a default is defined for the column and will emit [MaxLength] attributes for columns with a length, specifically varchar columns. It will also check for identity columns and it will add the “[Table]” attribute to the start of the class declaration to ensure EF gets the table name correct. (EF sometimes wants to pluralize table names. Maybe it’s just me.) I used the conversion table on MSDN’s SQL Server Data Type Mappings page to generate the right C# type for each column. One caveat though – I haven’t tested everything, and “user defined types” such as Geometry and Geography are all mapped as byte arrays.

There is one weakness that I have yet to be able to overcome, and that is handling cascading delete scenarios. SQL Server will automatically try to detect repeating cycles of cascades and will flag errors. Sometimes, such as the case above for “webpages_UsersInRoles” where there are multiple foreign keys, SQL Server can flag these even though the cascade won’t happen. You’ll need to use the Fluent API, with “.WillCascadeOnDelete(false)” to avoid this error. See this article for a better explanation.

There is one custom item in the generated attributes that are applied to the object properties, that is the “NoSpecialCharacters” attribute. This attribute is a one-liner that scans incoming character fields for symbols typically used in JavaScript and HTML and rejects the field if any of these are found. The definition is quite simple:

    public static class StringHelpers
    {
        /// <summary>
        /// Test for special characters in input data
        /// </summary>
        /// <param name="value"></param>
        /// <returns>false if there are special characters</returns>
        public static bool NoSpecialCharacters(this String value)
        {
            return (-1 == value.IndexOfAny(";<>\"[]&".ToCharArray()));
        }
    }

It’s not perfectly bulletproof, but it does go a long way towards preventing injection type attacks.

Accessing data through the created classes is pretty straightforward and there is a lot of information out there about how to do this. There is one thing that I would like to point out, that is that the [Required] attribute on the child foreign key relationships require accessing objects for editing and saving data in a particular manner.

    // accessing data for editing
    List<webpages_UsersInRoles> usersinroles = db.UserProfile
        .Where(p=>p.UserName==HttpContext.User.Identity.Name)
        .FirstOrDefault()
        .webpages_UsersInRoles.ToList();

    // Saving a new item
    db.UserProfile.Where(p=>p.UserName==HttpContext.User.Identity.Name)
        .webpages_UsersInRoles.Add(new_userinrole);

In the case of accessing data, the child items have to be accessed through the parent object. This sets up the foreign key relationship correctly; the same is true of saving a new object. While the child object can be created individually, it needs to be saved in relation to the correct parent object.

Read Full Post »

Well, hello there. It’s been a long (long!) time since I posted anything here. Since we’re well overdue for new content, I thought I’d post something quick and useful.

Any programmer who’s tried to write SQL queries is well aware that SQL does it’s own, limited, pattern matching for values in strings. As useful and powerful as SQL can be, it really needs better facilities for managing strings. In Microsoft SQL Server, that need can be answered through the use of a CLR function. That is, user written C# or VB (or any other CLR supported language) functions that can be called from your SQL statement to manipulate strings, implement complex business logic, or pretty much anything else. In this case, we’re going to add regular expression handling to SQL Server.

This journey began with the need to extract a business license number from a comment field. Unfortunately, the comment field held all sorts of information, such as who authorized the license, dates of one sort or another, phone numbers, and an unexpected multitude of ways to abbreviate “business license”. Not the sort of thing that native SQL could do – but could easily be handled through regular expression pattern matching. Unfortunately, SQL Server doesn’t support regular expressions. But – we can fix that!

There is a lot of information about writing CLR functions for SQL Server and much of it revolves around implementing regular expressions. This site is one of the best. However, the assembly presented there is in VB and I’m a C# guy. Their implementation also changes the order of parameters from the .NET Regular Expression object’s order, and I wanted to preserve the parameter order so that general reference material on .NET regular expressions would still be relevant – i.e. no hidden gotchas.

So, we’re off to writing our own CLR function for SQL. This turned out to be far simpler than expected, however I did run into one hiccup. The CLR function you create must be compiled for .NET 3.5 for SQL Server 2008. SQL Server 2012 will, I believe support .NET 4.0 and later. Several of the CLR regular expression implementations that you can find on the web include a step for creating a Regex object. My initial thoughts went along the lines of creating and then caching a Regex object in a static variable, and recreating the object whenever the search pattern changed. Ultimately, in the name of simplicity, I ended up using the Regex static methods and skipped manually creating any objects. Here’s my code for the Regex implementation:

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using System.Data.Sql;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Collections;

namespace SQLRegularExpression
{
    public static class RegExFunctions
    {

        [SqlFunction(IsDeterministic=true, IsPrecise=true)]
        public static SqlInt32 RegExOptions(SqlBoolean IgnoreCase, SqlBoolean Multiline, SqlBoolean ExplicitCapture, SqlBoolean Compiled,
            SqlBoolean Singleline, SqlBoolean IgnorePatternWhitespace, SqlBoolean RightToLeft, SqlBoolean ECMAScript, SqlBoolean CultureInvariant)
        {
            RegexOptions options;

            options = (IgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None)
                | (Multiline ? RegexOptions.Multiline : RegexOptions.None)
                | (ExplicitCapture ? RegexOptions.ExplicitCapture : RegexOptions.None)
                | (Compiled ? RegexOptions.Compiled : RegexOptions.None)
                | (Singleline ? RegexOptions.Singleline : RegexOptions.None)
                | (IgnorePatternWhitespace ? RegexOptions.IgnorePatternWhitespace : RegexOptions.None)
                | (RightToLeft ? RegexOptions.RightToLeft : RegexOptions.None)
                | (ECMAScript ? RegexOptions.ECMAScript : RegexOptions.None)
                | (CultureInvariant ? RegexOptions.CultureInvariant : RegexOptions.None);

            return (Int32) options;
        }

        [SqlFunction(IsDeterministic=true, IsPrecise=true)]
        public static SqlString RegExMatch(SqlString input, SqlString pattern, SqlInt32 options)
        {
            if (input.IsNull || pattern.IsNull) return null;
            return Regex.Match(input.Value, pattern.Value, (RegexOptions)options.Value).Value;
        }

        [SqlFunction(IsDeterministic=true, IsPrecise=true)]
        public static SqlInt32 RegExIndex(SqlString input, SqlString pattern, SqlInt32 options)
        {
            if (input.IsNull || pattern.IsNull) return 0;
            return Regex.Match(input.Value, pattern.Value, (RegexOptions)options.Value).Index;
        }

        [SqlFunction(IsDeterministic=true, IsPrecise=true)]
        public static SqlBoolean RegExIsMatch(SqlString input, SqlString pattern, SqlInt32 options)
        {
            if (input.IsNull || pattern.IsNull) return false;
            return Regex.IsMatch(input.Value, pattern.Value, (RegexOptions)options.Value);
        }

        [SqlFunction(IsDeterministic = true, IsPrecise = true)]
        public static SqlString RegExReplace(SqlString input, SqlString pattern, SqlString replacement, SqlInt32 options)
        {
            if (input.IsNull || pattern.IsNull) return null;
            return Regex.Replace(input.Value, pattern.Value, replacement.Value, (RegexOptions)options.Value);
        }

        [SqlFunction(IsDeterministic = true, IsPrecise = true)]
        public static SqlString RegExSqlReplace(SqlString input, SqlString pattern, SqlString replacement)
        {
            if (input.IsNull || pattern.IsNull) return null;
            return Regex.Replace(input.Value, pattern.Value, replacement.Value, RegexOptions.IgnoreCase | RegexOptions.Multiline);
        }

        [SqlFunction(IsDeterministic=true, IsPrecise=true)]
        public static SqlString RegExEscape(SqlString input)
        {
            if (input.IsNull) return null;
            return Regex.Escape(input.Value);
        }

        [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true, IsPrecise = true, Name = "RegExSplit",
            SystemDataAccess = SystemDataAccessKind.None, FillRowMethodName = "RegExSplitRow")]
        public static IEnumerable RegExSplit(SqlString input, SqlString pattern, SqlInt32 options)
        {
            if (input.IsNull || pattern.IsNull) return null;
            return Regex.Split(input.Value, pattern.Value, (RegexOptions)options.Value);
        }

        public static void RegExSplitRow(Object input, ref SqlString match)
        {
            match = new SqlString(input.ToString());
        }

        [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true, IsPrecise = true, Name = "RegExMatches",
            SystemDataAccess = SystemDataAccessKind.None, FillRowMethodName = "RegExMatchesRow")]
        public static IEnumerable RegExMatches(SqlString input, SqlString pattern, SqlInt32 options)
        {
            if (input.IsNull || pattern.IsNull) return null;
            return Regex.Matches(input.Value, pattern.Value, (RegexOptions)options.Value);
        }

        public static void RegExMatchesRow(Object input, ref SqlString match, ref SqlInt32 matchIndex, ref SqlInt32 matchLength)
        {
            Match m = (Match)input;
            match = new SqlString(m.Value);
            matchIndex = m.Index;
            matchLength = m.Length;
        }
    }
}

That’s it. So what’s going on here that makes this class callable from SQL server? Well, there are a couple of things. First and foremost is the attribute declaring that each of the main interface functions is a ‘SQLFunction‘. The properties of the SQLFunction attribute class help define to SQL Server how and what the method it’s applied to interfaces with SQL Server. While we’re not going to talk about this in detail, a little later we will examine the “FillRowMethodName” property. The other point to mention is the use of the SqlXxxx variable types both for paramters and return values. These types contain extra information for dealing with Null values from SQL Server. Compile this with an output type of ‘Class Library’ and then copy the .dll file onto your SQL server machine.

On the SQL Server side, we need to register our new assembly and define the methods as functions. This script locates the .dll file you just copied to the SQL Server machine and declares each function. You will need to put in the path to the location of the .dll in the ‘Create Assembly’ statement in the middle of the script.

sp_configure 'clr enabled', 1
RECONFIGURE WITH OVERRIDE
 
-- ************************************************************************************************
-- Remove Function Entry Points
-- ************************************************************************************************
 
IF EXISTS ( SELECT   1
            FROM     sys.objects
            WHERE    object_id = OBJECT_ID(N'dbo.RegExOptions') )
   DROP FUNCTION dbo.RegExOptions
 
IF EXISTS ( SELECT   1
            FROM     sys.objects
            WHERE    object_id = OBJECT_ID(N'dbo.RegExSplit') )
   DROP FUNCTION dbo.RegExSplit
go
 
IF EXISTS ( SELECT   1
            FROM     sys.objects
            WHERE    object_id = OBJECT_ID(N'dbo.RegExEscape') )
   DROP FUNCTION dbo.RegExEscape
go
 
IF EXISTS ( SELECT   1
            FROM     sys.objects
            WHERE    object_id = OBJECT_ID(N'dbo.RegExReplace') )
   DROP FUNCTION dbo.RegExReplace
go
-- IF EXISTS ( SELECT   1
--             FROM     sys.objects
--             WHERE    object_id = OBJECT_ID(N'dbo.RegExReplacex') )
--    DROP FUNCTION dbo.RegExReplacex
-- go
 
IF EXISTS ( SELECT   1
            FROM     sys.objects
            WHERE    object_id = OBJECT_ID(N'dbo.RegExIndex') )
   DROP FUNCTION dbo.RegExIndex
go
 
IF EXISTS ( SELECT   1
            FROM     sys.objects
            WHERE    object_id = OBJECT_ID(N'dbo.RegExIsMatch') )
   DROP FUNCTION dbo.RegExIsMatch
go
 
IF EXISTS ( SELECT   1
            FROM     sys.objects
            WHERE    object_id = OBJECT_ID(N'dbo.RegExMatch') )
   DROP FUNCTION dbo.RegExMatch
go
 
IF EXISTS ( SELECT   1
            FROM     sys.objects
            WHERE    object_id = OBJECT_ID(N'dbo.RegExMatches') )
   DROP FUNCTION dbo.RegExMatches
go
 
-- ************************************************************************************************
-- Remove the assembly from SQL Server
-- ************************************************************************************************

IF EXISTS ( SELECT   1
            FROM     sys.assemblies asms
            WHERE    asms.name = N'RegexFunction ' ) 
   DROP ASSEMBLY [RegexFunction]

-- ************************************************************************************************
-- Create Assembly reference
-- ************************************************************************************************

CREATE ASSEMBLY RegexFunction 
    FROM 'C:\\path\\to\\your\\assembly\\SQLRegularExpression.dll'
GO

-- ************************************************************************************************
-- Create Function Entry Points
-- ************************************************************************************************
 
CREATE FUNCTION [dbo].[RegExOptions]
      (
      @IgnoreCase bit,
        @MultiLine bit,
        @ExplicitCapture bit,
        @Compiled  bit,
        @SingleLine  bit,
        @IgnorePatternWhitespace  bit,
        @RightToLeft  bit,
        @ECMAScript  bit,
        @CultureInvariant  bit
        )
returns int
AS EXTERNAL NAME
   RegexFunction.[SQLRegularExpression.RegExFunctions].RegExOptions
 
go
 
CREATE FUNCTION [dbo].[RegExIsMatch]
   (
    @Input NVARCHAR(MAX),
    @Pattern NVARCHAR(4000),
    @Options int
   )
RETURNS BIT
AS EXTERNAL NAME
   RegexFunction.[SQLRegularExpression.RegExFunctions].RegExIsMatch
GO
 
CREATE FUNCTION [dbo].[RegExIndex]
   (
    @Input NVARCHAR(MAX),
    @Pattern NVARCHAR(4000),
    @Options int
   )
RETURNS int
AS EXTERNAL NAME
   RegexFunction.[SQLRegularExpression.RegExFunctions].RegExIndex
GO
 
CREATE FUNCTION [dbo].[RegExMatch]
   (
    @Input NVARCHAR(MAX),
    @Pattern NVARCHAR(4000),
    @Options int
 )
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME
   RegexFunction.[SQLRegularExpression.RegExFunctions].RegExMatch
GO
 
CREATE FUNCTION [dbo].[RegExEscape]
   (
    @Input NVARCHAR(MAX)
   )
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME
   RegexFunction.[SQLRegularExpression.RegExFunctions].RegExEscape
GO

CREATE FUNCTION [dbo].[RegExSplit]
   (
    @Input NVARCHAR(MAX),
    @Pattern NVARCHAR(4000),
    @Options int
   )
RETURNS TABLE (Match NVARCHAR(MAX))
AS EXTERNAL NAME
   RegexFunction.[SQLRegularExpression.RegExFunctions].RegExSplit
GO
 
CREATE FUNCTION [dbo].[RegExReplace]
   (
    @Input NVARCHAR(MAX),
    @Pattern NVARCHAR(4000),
    @Repacement NVARCHAR(MAX),
    @Options int
   )
RETURNS  NVARCHAR(MAX)
AS EXTERNAL NAME
   RegexFunction.[SQLRegularExpression.RegExFunctions].RegExReplace
GO
 
CREATE FUNCTION [dbo].[RegExMatches]
   (
    @Input NVARCHAR(MAX),
    @Pattern NVARCHAR(4000),
    @Options int
   )
RETURNS TABLE (Match NVARCHAR(MAX), MatchIndex INT, MatchLength INT)
AS EXTERNAL NAME
   RegexFunction.[SQLRegularExpression.RegExFunctions].RegExMatches
GO

The one thing that astonished me about all this was that it worked the first time. That never happens! Anyway, there is one thing about these that I would like to point out: the two functions that return tables instead of a value. These are RegExSplit and RegExMatches. If you scroll up and look at the C# definitions, you will see a couple of things. First, these two functions return an IEnumerable instead of a value, and that each have the SQLFunction attribute parameter “FillRowMethodName” set to a function also defined in the C# code. What happens with these is that the base function does it’s thing and returns a list (IEnumerable) of results. The SQL interface process then calls the method defined in the “FillRowMethodName” property for each result entry in the IEnumerable. The first paramter to the fill row method is the enumerated object returned by the base call (RegExSplit or RegExMatches), and the second and later parameters are all defined as ‘ref’ parameters. You’ll notice that the SQL declaration for these functions returns a table whose columns exactly match those ‘ref’ parameters on the fill row method. So, each row of the result table is the product of calling the fill row method for each entry in the IEnumerable list returned by the base method.

So, how to use this? You can use the declared functions in the same way that you would use any other SQL Function. In this case, I’m using the RegExMatch function to extract the aforementioned business license number from the comments field, and using the RegExReplace function to remove terminal abbreviations for Street or Road (‘ST’ or ‘RD’, etc.) from the street name.

select 
    dbo.RegExMatch(comments, '\d{7}|d{6}', 0) as license, 
    street_number, 
    dbo.RegExReplace(street_name, '\bLN\Z|\bAV\Z|\bDR\Z|\bPL\Z|\bRD\Z|\bCR\Z|\bCT\Z|\bBL\Z|\bST\Z|\bWY\Z|\bHY\Z', '', 0) as name,
    city,
    state,
    zip
from mytable z

A final word on the use of CLR functions. Depending on your security setup, the use of external functions can be considered a security risk. You will need to evaluate the business need for functions like this against the risks involved. You should also be wary of using CLR functions in SQL Server to implement functionality that architecturally belongs in the business or presentation layers. This I would strongly encourage you to not do. Put things where they belong, even if it’s difficult, and you’ll be rewarded with a better performing, more maintainable system overall.

Read Full Post »