OneDealer contains a module which provides a duplicate check mechanism for the following:

  • Business Partners
    • BusinessPartner

  • Contact Persons
    • IDMS_ContactPersonView
    • IDMS_MAINCNTCTPRSNVIEW
  • Addresses
    • IDMS_BPAddressView

This mechanism is based on an XML file which configures how the duplicate check operates over the data of the above entities.

XML Configuration

The configuration for the duplicate check is an XML file, named ProfileConfiguration.xml (~/Files/DuplicateCheckProfiles/ProfileConfiguration.xml).

The main structure is presented below:

<EntityDuplicateCheckList>
    <!-- A list of duplicate check for entities, either models or views -->
    <EntityDuplicateChecks>

        <EntityDuplicateCheck>
            <!-- This the type of the object where the data will be stored -->
            <EntityName>ENTITY_NAME</EntityName>

            <!-- A list of duplicate check rules -->
            <DuplicateCheckRules>

                <!-- A duplicate check rule -->
                <DuplicateCheckRule>
                    <!-- A name for this rule. This is used as parameter at the method that initiates the duplicate check -->
                    <DuplicateCheckSourceName>SOURCE_NAME</DuplicateCheckSourceName>

                    <!--
                    The tenant's identifier. If we add this element, then the rule applies only to this tenant. Otherwise, it's a base rule

                    The tenant name must be the same with the directory name at the Installations directory
                    -->
                    <DuplicateCheckTenantName>TENANT_NAME</DuplicateCheckTenantName>

                    <!-- This is the type of the object where the data are coming from -->
                    <SourceModelName>SOURCE_OBJECT_NAME</SourceModelName>

                    <UpdateMapProfileName>MAP_PROFILE_NAME</UpdateMapProfileName>

                    <!-- This is basically the list of rules that are going to be executed according to the order at each set -->
                    <QueryStepProfiles> 
                        <!-- The key word here is recursiveness. Due to the way that the duplicate check mechanism has been developed, we can nest the filters we want to add which allows us to make complex filters.
                        
                        The most notable example is the phones duplicate check.
                        -->

                        <!-- The 1st query (set of rules) that will be run -->
                        <DuplicateCheckQueryStep>

                            <!-- This element defines the order of execution of the set of rules -->
                            <Order>0</Order>

                            <QueryStep>
                                <DuplicateCheckQueryStep>
                                    <QueryProfileProperty>
                                        <!-- This is the name of the property from the source entity (Check the XML element <DuplicateCheckSourceName>) -->
                                        <SourceProperty>SOURCE_PROPERTY</SourceProperty>

                                        <!-- Possible operators: Equals, NotEqual, Contains, GreaterThan, GreaterThanOrEqual, LessThanOrEqual, LessThan, In, StartsWith -->
                                        <MappingOperator>LOGICAL_OPERATOR</MappingOperator>

                                        <!-- This is the name of the property from the target entity (Check the XML element <EntityName>) -->
                                        <DestinationProperty>TARGET_PROPERTY</DestinationProperty>
                                    </QueryProfileProperty>

                                    <!--
                                    Possible operators: And, Or, In

                                    This is the "glue" between each rule. We can omit the following XML element and the default operator will be the "And".
                                    -->
                                    <CombineOperator>LOGICAL_OPERATOR</CombineOperator>
                                </DuplicateCheckQueryStep>

                                <DuplicateCheckQueryStep>
                                    <QueryProfileProperty>
                                        <!-- This is the name of the property from the target entity (Check the XML element <EntityName>) -->
                                        <DestinationProperty>TARGET_PROPERTY</DestinationProperty>

                                        <!-- Possible operators: Equals, NotEqual, Contains, GreaterThan, GreaterThanOrEqual, LessThanOrEqual, LessThan, In, StartsWith -->
                                        <MappingOperator>LOGICAL_OPERATOR</MappingOperator>

                                        <!-- Here can add a static value where it can be used fior filtering. For example, the most notable purpose for this is the "noemail@<domain>" text which usually refers to a placeholder email -->
                                        <IsEqualToStaticValue>STATIC_VALUE</IsEqualToStaticValue>
                                    </QueryProfileProperty>

                                    <!--
                                    Possible operators: And, Or, In

                                    This is the "glue" between each rule. We can omit the following XML element and the default operator will be the "And".
                                    -->
                                    <CombineOperator>LOGICAL_OPERATOR</CombineOperator>
                                </DuplicateCheckQueryStep>
                            </QueryStep>

                        </DuplicateCheckQueryStep>

                        <!-- The 2nd query (set of rules) that will be run -->
                        <DuplicateCheckQueryStep>

                            <!-- This element defines the order of execution of the set of rules -->
                            <Order>1</Order>

                            <!-- Can be the same structure as the above query -->

                        </DuplicateCheckQueryStep>

                    </QueryStepProfiles>

                </DuplicateCheckRule>

            </DuplicateCheckRules>
            <MapProfiles></MapProfiles>
        </EntityDuplicateCheck>
    </EntityDuplicateChecks>
</EntityDuplicateCheckList>

As it was pointed out above, because of the recursive nature of the duplicate check, we can nest set of rules.


The key XML elements here are the <DuplicateCheckQueryStep> and <QueryStep>. These 2 elements are responsible for nesting rules and generally constructing the rules.

In order to make it more understandable, we can separate the construction of the rules in 2 categories:

  • The non-recursive ones
  • The recursive ones

In the non-recursive ones, we have the following structure:

<DuplicateCheckQueryStep>
    <QueryStep>
        <DuplicateCheckQueryStep>
            ...
        </DuplicateCheckQueryStep>
    </QueryStep>
</DuplicateCheckQueryStep>

In the recursive ones, we have the following structure:

<DuplicateCheckQueryStep>
    <!-- This is the root <QueryStep> -->
    <QueryStep>
        <!-- This is a non-recursive step -->
        <!-- we may have more than one non-recursive <DuplicateCheckQueryStep> -->
        <DuplicateCheckQueryStep>
            ...
        </DuplicateCheckQueryStep>

        <!-- This is a recursive <DuplicateCheckQueryStep> -->
        <DuplicateCheckQueryStep>
            <QueryStep>
                <DuplicateCheckQueryStep>
                    ...
                </DuplicateCheckQueryStep>
            </QueryStep>
        </DuplicateCheckQueryStep>
    </QueryStep>
</DuplicateCheckQueryStep>


So, to summarize, in order to properly nest duplicate check rules we can use the the non-recursive structure as many times as needed in order to structure our rule correctly.

Functionality

At the OneDealer solution, inside the Modules directory, there is a directory named DuplicateCheck which contains a project with the same name. The project has the usual structure of a "BusinessLayer" with Managers, Models etc.


In the Manager directory, there are to 2 classes:

  • DuplicateCheckProfileProvider
    • It contains the method which parses the XML configuration
  • WebDuplicateCheckManager
    • It contains the DuplicateCheck method which is the entry point
      • There are 2 DuplicateCheck methods. The one that is actually used is this with the 2 parameters in its signature
    • Several other helper methods

In the Models directory, there are 2 classes which contain most of the functionality of the duplicate check:

  • DuplicateCheckRule
    • Contains the main function of the duplicate check which loops through the rules and constructs the filter that eventually is sent to the DAL
      • During the execution of each set of rules, if a result was found using the constructed filters for that rules, then the method breaks from the loop, ignoring the rest of the rules and returns the results.
    • DuplicateCheckQueryStep
      • This class is utilized better when there are recursive rules. However, it is used even with non-recursive ones

The rest of the files are supplementary to the core functionality.


Version 2.60.0 Onwards Refactor

From this version onwards the implementation of this functionality regarding base and overriding rules has changed.

The below tags that were used to indicate if a rule was relevant a specific tenant are no longer used.

Tenant Definition
<DuplicateCheckTenantName>TENANT_NAME</DuplicateCheckTenantName>

Now any rules that are to be applied across all installation are defined in the ProfileConfiguration.xml located in "~/Files/DuplicateCheckProfiles/" in the OneDealer.MVC project.

Any rule that is specific to one tenant or if a rule needs to be overridden for a specific tenant then it needs to be defined in a ProfileConfiguration.xml file under that customer's folder in the same relative path as above.

The tenant definition tags are not to be used any more.

Based on the files location the code will understand if the content is tenant specific rule or if it only has the rules of the base configuration.

In the method GetAllDuplicateCheckProfiles of the DuplicateCheckProfileProvider, the below line if an overriding file exists for the installation code, and if a file does not exist there, it will return the absolute path of the base configuration

var pathTenant = _systemPathProvider.GetAbsolutePath(relativePath);

The resulting path is compared to the absolute path of the base:

  1. if they match then the tenant has no overriding configuration and the base collection of rules is returned
  2. if they do not match, then:
    1. The overriding configuration is read and parsed
    2. Rules are matched according to EntityName and DuplicateCheckSourceName and SourceModelName:
      1. If a match is found then then the base QueryStepProfiles are replaced with those of the tenant's configuration.
      2. if at either level no match is found then the tenants rule is added to the base collection of rules.
    3. the final collection is returned.

Note:

Any previous logic to check for the tenant definition tags has been removed as well.


Touch points

  • BPAddressManager
    • AddOrUpdateAddressToBusinessPartner
  • ServiceActionController class
    • SaveNewJobCard method
  • BPContactPersonManager
    • CheckCreateOrUpdateCompanyBusinessPartner
    • AddOrUpdateContactPersonFromFormViewModel
      • Used from the BP/CP create pages
  • BusinessPartnerManager
    • CheckAndUpdateOrCreateBusinessPartner_Local
  • InquiryAssignmentManager
    • InquiryContactQualification: Called when we are entering the Inquiry page which is triggered by Ajax in order to search for duplicates
  • SalesOpportunityManager
    • CreateContactFromConfiguratorRequest: Most probably called when creating a Qualified Inquiry
Write a comment…