Question

With the following code, I used csc to compile the dll. I then added the assembly to sql server 2014 using

CREATE ASSEMBLY ReplaceMultiWord from 'd:\bcp\ReplaceMultiWord.dll' WITH PERMISSION_SET = SAFE;

Then added the first function successfully with

CREATE FUNCTION dbo.ReplaceMultiWord
(@inputString AS NVARCHAR(MAX), @replacementSpec AS XML) RETURNS NVARCHAR(MAX)
AS
EXTERNAL NAME ReplaceMultiWord.UserDefinedFunctions.ReplaceMultiWord;

However, when I tried adding the second function with

CREATE FUNCTION dbo.CanReplaceMultiWord
(@inputString AS NVARCHAR(MAX), @replacementSpec AS XML) RETURNS BIT
AS
EXTERNAL NAME ReplaceMultiWord.UserDefinedFunctions.CanReplaceMultiWord;

I received the error

Could not find method 'CanReplaceMultiWord' for type 'UserDefinedFunctions' in assembly 'ReplaceMultiWord'

Below is my code. Why can't sql server see that second function?

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Xml;

public partial class UserDefinedFunctions
{
    //TODO: Concurrency?
    private static readonly Dictionary<string, ReplaceSpecification> cachedSpecs =
                        new Dictionary<string, ReplaceSpecification>();

    [SqlFunction(IsDeterministic = true,
                 IsPrecise = true,
                 DataAccess = DataAccessKind.None,
                 SystemDataAccess = SystemDataAccessKind.None)]
    public static SqlString ReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)
    {
        //TODO: Implement something to drop things from the cache and use a shorter key.
        string s = replacementSpec.Value;
        ReplaceSpecification rs;

        if (!cachedSpecs.TryGetValue(s, out rs))
        {
            var doc = new XmlDocument();
            doc.LoadXml(s);
            rs = new ReplaceSpecification(doc);
            cachedSpecs[s] = rs;
        }

        string result = rs.GetResult(inputString.ToString());
        return new SqlString(result);
    }

    [SqlFunction(IsDeterministic = true,
             IsPrecise = true,
             DataAccess = DataAccessKind.None,
             SystemDataAccess = SystemDataAccessKind.None)]
    public static SqlBoolean CanReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)
    {
        string s = replacementSpec.Value;
        ReplaceSpecification rs;

        if (!cachedSpecs.TryGetValue(s, out rs))
        {
            var doc = new XmlDocument();
            doc.LoadXml(s);
            rs = new ReplaceSpecification(doc);
            cachedSpecs[s] = rs;
        }

        return rs.IsMatch(inputString.ToString());
    }

    internal class ReplaceSpecification
    {
        internal ReplaceSpecification(XmlDocument doc)
        {
            Replacements = new Dictionary<string, string>();

            XmlElement root = doc.DocumentElement;
            XmlNodeList nodes = root.SelectNodes("x");

            string pattern = null;
            foreach (XmlNode node in nodes)
            {
                if (pattern != null)
                    pattern = pattern + "|";

                string find = node.Attributes["find"].Value.ToLowerInvariant();
                string replace = node.Attributes["replace"].Value;
                pattern = pattern + Regex.Escape(find);
                Replacements[find] = replace;
            }

            if (pattern != null)
            {
                pattern = "(?:" + pattern + ")";
                Regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
            }
        }
        private Regex Regex { get; set; }

        private Dictionary<string, string> Replacements { get; set; }

        internal string GetResult(string inputString)
        {
            if (Regex == null)
                return inputString;

            return Regex.Replace(inputString,
                                 (Match m) =>
                                 {
                                     string s;
                                     if (Replacements.TryGetValue(m.Value.ToLowerInvariant(), out s))
                                     {
                                         return s;
                                     }
                                     else
                                     {
                                         throw new Exception("Missing replacement definition for " + m.Value);
                                     }
                                 });
        }

        internal bool IsMatch(string inputString)
        {
            if (Regex == null)
                return false;
            return Regex.IsMatch(inputString);
        }
    }
}
Was it helpful?

Solution 2

With the assistance from srutzky I found the (dumb) error. Always check that you've updated the CREATE ASSEMBLY line to pull from the latest version of your DLL. When I added the second function to the DLL, I used a different name so that I could revert if I made an error, but forgot to update the file path correctly. Once I did that, this mysterious error went away.

OTHER TIPS

There is nothing wrong with your code, at least not the code that is posted in the question. I copied and pasted it into Visual Studio 2015 and it compiled and deployed without errors. I thought perhaps you might have an overloaded method for CanReplaceMultiWord since those are not allowed, but I tested that scenario by creating an empty method of nearly the same signature, just changing SqlString into SqlChars. It also compiled and deployed just fine but this time did not create dbo.CanReplaceMultiWord. I tried creating manually via your CREATE FUNCTION statement but got an error about there being "more than one method" for CanReplaceMultiWord and not being able to overload; I did not get anything about "cannot find method".

So, I think perhaps you might, somehow, have a non-printable character (control character maybe?) in that method name in the CREATE FUNCTION statement. Try deleting that EXTERNAL NAME AS line and re-typing it. Although, I did copy and paste your code from the "edit" page so if you copied and pasted into the question, then it usually gets copied there as well (unless their either don't or maybe get cleaned on the way into the system, not sure). Still, I still find it most likely that something is wrong with the method name in the CREATE FUNCTION statement (somehow, since it works on my system) since if you have the wrong Assembly name, you get:

Assembly 'xxxxxxxxxx' was not found in the SQL catalog of database 'your_db_name'.

And the correct Assembly name but wrong Class name returns:

Could not find Type 'yyyyyyyyyyy' in assembly 'xxxxxxxxxx'.

Also, I highly recommend using Visual Studio to do the compilation. The "Community Edition" is free, so no (good) reason to not be using it. There are several options (many really) for passing into csc.exe that it handles nicely. Even if you don't use it for final deployment (I don't), it is great for development and testing.

P.S. One benefit of using VS is that it deploys by converting the DLL bytes into a hex string so that it can do CREATE ASSEMBLY FROM 0x4D...., which is not only more portable, but you can't possibly have the wrong version of the Assembly (assuming you are using the most recent deploy / create script).

SIDE NOTE:

You want to be careful when using the RegexOptions.Compiled option with object-based RegEx due to the compiled definition getting discarded once the method goes out of scope. I could be reading your code wrong, but it looks like the RegEx is really only executed once, which is a bad case for using RegexOptions.Compiled. You either want to get rid of RegexOptions.Compiled or switch to the static method (unless, of course, I am misreading the code ;-). Please see the MSDN page for Regular Expression Options for more details.

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top