Skip to content

nicogis/DoubleMetaphoneXAF

Repository files navigation

Double Metaphone XAF

This is a PoC to use DoubleMetaphone algorithm in XAF

  • Build DoubleMetaphone project

  • Register dll in SQL Server

    You can follow these steps (here I am using Northwind):

        ALTER DATABASE Northwind
        SET TRUSTWORTHY ON;

        CREATE ASSEMBLY DoubleMetaphone
        FROM 'C:\Temp\DoubleMetaphone.dll' WITH PERMISSION_SET = SAFE

        ALTER DATABASE Northwind
        SET TRUSTWORTHY OFF;

Visualize list of assembly in db

    SELECT * from sys.assemblies

From Assembly select DoubleMetaphone and create script from assembly in clipboard create script from assembly Clipboard:

CREATE ASSEMBLY [DoubleMetaphone]
FROM 0x4D5A90...
WITH PERMISSION_SET = SAFE
GO

Copy 0x4D5A90... in variable @asmBin and run this script to add assembly in trusted assembly

USE master;
GO
DECLARE @clrName nvarchar(4000) = 'doublemetaphone, version=0.0.0.0, culture=neutral, publickeytoken=null, processorarchitecture=msil'
DECLARE @asmBin varbinary(max) = 0x4D5A90...;
DECLARE @hash varbinary(64);

SELECT @hash = HASHBYTES('SHA2_512', @asmBin);

EXEC sys.sp_add_trusted_assembly @hash = @hash,
                                 @description = @clrName;

Visualize list of assembly trusted

select * FROM sys.trusted_assemblies

Create function in db

CREATE FUNCTION dbo.udf_double_metaphone(@value nvarchar(max)) 
RETURNS nvarchar(max)   
AS EXTERNAL NAME DoubleMetaphone.DoubleMetaphoneUDT.DoubleMetaphoneExec;   
GO 

Enable crl


EXEC sp_configure 'clr enabled', 1;  
RECONFIGURE;  
GO 

In project XAF agnostic module create a custom function

    public class DoubleMetaphone : ICustomFunctionOperatorFormattable
    {
        public string Name
        {
            get
            {
                return "DoubleMetaphone";
            }
        }
        public object Evaluate(params object[] operands)
        {
            throw new NotImplementedException();
        }
        public Type ResultType(params Type[] operands)
        {
            return typeof(object);
        }
        static DoubleMetaphone()
        {
            DoubleMetaphone instance = new DoubleMetaphone();
            if (CriteriaOperator.GetCustomFunction(instance.Name) == null)
            {
                CriteriaOperator.RegisterCustomFunction(instance);
            }
        }
        public static void Register()
        {
        }

        public string Format(Type providerType, params string[] operands)
        {
            if (providerType == typeof(MSSqlConnectionProvider))
            {
               
                return $"dbo.udf_double_metaphone({operands[0]}) = {operands[1]}";
            }
                
            throw new NotSupportedException($"This provider is not supported: {providerType.Name}");           
        }
    }

In module.cs register the custom function

public sealed partial class DoubleMetaphoneXAFModule : ModuleBase {
        public DoubleMetaphoneXAFModule() {
            InitializeComponent();
            Functions.DoubleMetaphone.Register();
            ....
        }
        
    }

Add a controller to custom search standard

public partial class VCSearchDoubleMetaphone : ViewController
    {
        // Use CodeRush to create Controllers and Actions with a few keystrokes.
        // https://docs.devexpress.com/CodeRushForRoslyn/403133/
        public VCSearchDoubleMetaphone()
        {
            InitializeComponent();
            // Target required Views (via the TargetXXX properties) and create their Actions.
            TargetObjectType = typeof(Customers);
        }
        private FilterController standardFilterController;
        protected override void OnActivated()
        {
            base.OnActivated();
            standardFilterController = Frame.GetController<FilterController>();
            if (standardFilterController != null)
            {
                
                standardFilterController.CustomBuildCriteria += StandardFilterController_CustomBuildCriteria;
            }
        }

        private void StandardFilterController_CustomBuildCriteria(object sender, CustomBuildCriteriaEventArgs e)
        {
            if (string.IsNullOrEmpty(e.SearchText))
            {
                e.Handled = false;
                return;
            }
            
            e.Criteria = CriteriaOperator.Parse($"DoubleMetaphone('{e.SearchText}', [CompanyNameDoubleMetaphone]) or CharIndex('{e.SearchText}', [CompanyName]) > -1");
            e.Handled = true;    
        }

        
        protected override void OnDeactivated()
        {
            if (standardFilterController != null)
            {
                standardFilterController.CustomBuildCriteria -= StandardFilterController_CustomBuildCriteria;
            }
            base.OnDeactivated();
        }
    }

In db Northwind add the field CompanyNameDoubleMetaphone in table Customers

USE [Northwind]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER TABLE Customers
ADD [CompanyNameDoubleMetaphone]  AS ([dbo].[udf_double_metaphone]([CompanyName])) PERSISTED
GO

In table customers I have added three similar strings (CompanyName field)

'Blauer See    Delikatessen sa'
'Blauer Se    Delikatessen s.a.'
'Blaer Se    Deliktesse s.a.'

and for example I search for 'Blaer Se Delikatessen' then I match all strings

search