Die Dublettenprüfung soll verhindern, dass Benutzer gleiche bzw bereits existierende Datensätze innerhalb einer Business App Instanz mehrfach anlegen. Die Prüfung erfolgt wahlweise beim Speichern eines Datensatzes oder direkt bei der Eingabe von Daten über den Vergleich generierter Phonetikzahlenfolgen. Diese Zahlenfolgen werden generell bei jedem Speichern eines Datensatzes nach Bedarf aktualisiert bzw. initial beim Start der Anwendung erzeugt. Die eigentliche Prüfung erfolgt über eine „beginnt mit“ Suche auf diese phonetischen Zahlenfolgen.
Die Dublettenprüfung wird über sog. Adapterklassen gesteuert, die in Projekten erstellt oder auch überschrieben werden können. Die Funktionalität selbst ist stark UI-abhängig und als Hilfe für den Endanwender gedacht; sie soll nicht programmatisch aufgerufen werden.
Bei BA.CRM werden zwei vorgefertigte Adapter mitgeliefert (Firmen & Kontakte), welche im Folgenden als Beispiele herangezogen werden.

Dublettenprüfadapter

Zur Steuerung der Dublettenprüfung werden wie schon erwähnt „Adapter“ verwendet, die programmatisch erstellt werden müssen. Diese zu erstellenden Klassen müssen einen generischen Typen besitzen, der quasi die Basisklasse des zu überprüfenden Datentyps darstellt.

Im Falle des ausgelieferten Firmenadapters ist die Deklaration wie folgt:

public class CompanyAdapter<T> : DuplicateSearchAdapterBase<T> where T : OrmCRMCompany

Das bedeutet: Streng typisiert verwendet dieser Adapter die Datentabelle OrmCRMCompany und funktioniert damit auch mit allen davon abgeleiteten Klassen (z. B. die interne -Custom-Klasse oder vom Konfigurator der Anwendung erstellte Ableitungen).

Die Adapterklasse muss außerdem mit Attributen dekoriert werden, welche der Prüfungsfunktionalität mitteilen, welche Datentabelle hier geprüft wird ([DuplicateSearchMainDataSource]), welche Datentabelle dazu gehören ([DuplicateSearchSubDataSource]), welche Felder geprüft werden sollen ([DuplicateSearchFieldDefinition] & [DuplicateSearchSubRecordFieldDefinition]) und ob und wenn welche bestehenden Relationen bei Änderungen geprüft werden sollen ([DuplicateSearchRelationDefinition]).

Beschreibung der Attribute

DuplicateSearchMainDataSource

Dieses Attribut definiert, welche Datentabelle primär auf Dubletten geprüft wird. Es darf pro Projekt immer nur einen Adapter mit der Angabe einer bestimmten Datentabelle existieren. Die Angabe von zwei oder mehr Adaptern mit der gleichen Datentabelle führt zu undefiniertem Verhalten. Ebenso darf dieses Attribut pro Adapter nur einmalig verwendet werden.

Parameter:

  • dataSourceGuid Guid der Datentabelle

DuplicateSearchSubDataSource

Über dieses Attribut werden weitere Datentabellen deklariert, welche mit der Datentabelle in irgendeiner Verbindung stehen und die in die Dublettenprüfung einbezogen werden sollen. Das Attribut kann an einem Adapter mehrfach angegeben werden.

Parameter:

  • dataSourceGuid Guid der Datentabelle

DuplicateSearchFieldDefinition

Mit dem Attribut DuplicateSearchFieldDefiniton wird festgelegt, dass ein Feld dublettenrelevant ist und sich somit Änderungen auf die Anzahl der möglichen Dubletten auswirken.

Parameter:

  • dataSourceGuid Guid der Datentabelle
  • fieldName Name des Felds in der DB
  • isMandatoryForDuplicateSearch Pflichtfeld, ohne dessen Inhalt eine Dublettensuche garnicht erst gestartet wird
  • indexPriority Indexpriorität, beeinflusst die Reihenfolge der Spalten in einem Index in der DB. Kleinere Zahlen sind wichtiger, sprich Felder mit hoher selektivität oder solche, die am ehesten ausgefüllt werden, sollten kleinere Zahlen haben. Felder die IsMandatory gesetzt haben, sind im DB-Index aber immer ganz vorne und werden erst an zweiter Stelle nach Priorität sortiert
  • skipPhoneticTranslation Das Feld wird trotz Dublettenrelevanz nicht phonetisch übersetzt. Sinnvoll bei Zahlenfeldern wie PLZ, da Zahlen in der Kölner Phonetic ignoriert werden
  • mandatoryGroupId Damit die Suche nach Dubletten überhaupt gestartet wird, muss pro Gruppe, die hier vergeben wird, mindestens ein Feld ausgefüllt sein.
    Das Setzen von mandatoryGroupId setzt automatisch isMandatoryForDuplicateSearch auf true.

DuplicateSearchSubRecordFieldDefinition

Dieses Attribut wird speziell dafür verwendet, um Felder zu erfassen, die sich auf Teil-Datensätzen einer Datentabelle befinden. Es erbt von DuplicateSearchFieldDefinition und implementiert alle dessen Parameter, fügt aber noch weitere zur Identifikation eines bestimmten Teildatensatzes hinzu.

Parameter:

  • subtableFieldName Name des Teil-Datensatzfeldes der Datentabelle (z. B. „Addresses“ auf Firmen und Kontakten in BA.CRM)
  • sortOrder Der Teil-Datensatz mit dem angegebenen Index soll bei der Dublettenprüfung einbezogen werden. Die Angabe von uniqueKey überschreibt dies.
  • uniqueKey Teil-Datensätze können auch über ihren eindeutigen Schlüssel identifiziert werden (z. B. der Anschriftentyp bei Anschriften in BA.CRM, um eine bestimmte Anschrift zu adressieren).

DuplicateSearchRelationDefinition

Hier wird definiert, welche Relation zwischen MainDataSource und SubDataSource verfolgt werden soll, im Beispiel-Szenario 1 betrifft das die primäre Anschrift. Es ist zu beachten, dass diese Relationen nur geprüft werden können, wenn sie in einem Dialog über dem Hauptdatensatz (MainDataSource) geöffnet werden, und nicht etwa im eigenen Tab. Das Attribut kann mehrfach vorkommen.

Parameter:

  • targetDataSourceGuid Datentyp-Guid des Relationsziels
  • sourceDataSourceGuid Datentyp-Guid der Relationsquelle
  • relationTypeGuid Art der zu prüfenden Relation

Beschreibung der überschreibbaren Methoden

GetDuplicateSearchBaseQuery

Die Funktion GetDuplicateSearchBaseQuery enthält die Logik zur Ermittlung der zu selektierenden Daten.

Wird diese Methode nicht explizit überschrieben, so wird hier ein IQueryable<T> aus allen lesbaren Elementen des Typs T zurückgeliefert (der Typ <T> ist der generische Typ des Adapters).

Der ausgelieferte Kontaktadapter überschreibt diese Methode beispielsweise, weil Dubletten nicht über alle Kontakte sondern nur über alle Kontakte der gleichen Firma geprüft werden sollen:

public override IQueryable<T> GetDuplicateSearchBaseQuery(T mainRecord, Session session)
{
    if (mainRecord is OrmContact contact && contact.GetFirstFoundParentOrm<OrmCRMCompany>() is OrmCRMCompany company)
        return Api.ORM.GetQueryWithReadPermissions<T>(session).Where(cont => company.RelatedContacts.Any(ff => ff == cont));

    return Api.ORM.GetQueryWithReadPermissions<T>(session);
}

GetCurrentRecordTitle

Mit GetCurrentRecordTitle wird implementiert, wie der Titel eines Datensatzes ermittelt wird. Dabei geht es in den jeweiligen Adaptern um die Live-Daten, die zu den gespeicherten Daten abweichen.

In der Basisimplementierung liefert diese Methode den EntityTitle des gerade editierten Hauptdatensatzes (z. B. Firma) zurück. Sollte dieser nicht ermittelbar sein, ist der Rückgabewert null.

Hier als Beispiel die Implementierung dieser Methode im ausgelieferten Firmenadapter:

public override string GetCurrentRecordTitle(T mainRecord, Guid recordId, DuplicateChangeInfo[] changeInfos)
{
    if (changeInfos != null && changeInfos.FirstOrDefault(ff => ff.ElementName == "Name")?.Value is string name)
        return name;
eturn base.GetCurrentRecordTitle(mainRecord, recordId, changeInfos);
}

Das Ziel ist es hier, aus den Daten der Live-Prüfung die Änderungen am Feld „Name“ zu ermitteln. Sollte es keine Änderung an diesem Feld geben, wird die Basisimplementierung verwendet.

Verwendete Phonetik

Die Art und Weise, wie die für die Dublettenprüfung relevanten Daten in Phonetiken umgesetzt wird, ist in Projekten über Dependency-Injection anpassbar. Im Normalzustand wird die sog. „Kölner Phonetik“ verwendet.

Anmeldung der Phonetik:

Bind<IPhonetics>().To<DefaultPhonetics>();
Implementierung der Phonetik:
public class DefaultPhonetics : IPhonetics
{
   public string GetPhonetics(string input) => ColognePhonetics.GetPhonetics(input);
}

Notwendige Anpassungen bei der Erstellung eigener Prüfungen

Es muss lediglich ein neuer Adapter implementiert werden. Beim nächsten Anwendungsstart werden automatisch die benötigten Spalten angelegt und die Berechnung der Phonetiken durchgeführt.

Wenn Dublettenprüfungen auf Datentabellen erstellt werden, die bereits in einer anderen Form auf Dubletten geprüft wurden (z.B. erst Anschrift in Kombination mit Kontakt, jetzt noch Anschrift in Kombination mit Firma), und sich dabei die dublettenrelevanten Felder unterscheiden, kann es hilfreich sein, alle zugehörigen Indizes auf die Phonetik-Spalten dieser Tabellen auf der Datenbank zu löschen. Diese werden nach einem optimierten Verfahren erneut angelegt.

Wenn ein Adapter wieder gelöscht wird, sollten die zugehörigen Phonetik-Spalten in den Tabellen manuell ebenfalls gelöscht werden.

Beispiel: Dublettenprüfung für Firmen

Grundsätzlich soll gelten:
Bei Firmen liegt möglicherweise eine Dublette vor,

  • wenn sich der Firmenname stark ähnelt und
  • wenn Straße und Postleitzahl der primären Anschrift ebenfalls phonetisch ähnlich sind.

Szenario 1 (alte Situation in BA.CRM):
Anschriften von Firmen liegen in getrennten Datensätzen, die über Relationen mit der Firma verknüpft sind. Es existiert eine Relation, um eine zur Firma gehörende Anschrift als Primäranschrift zu klassifizieren.

Die Attribute hierfür müssen wie folgt angegeben werden:

    [DuplicateSearchMainDataSource(EnumDataSourceExtension.CompanyGuid)]
    [DuplicateSearchSubDataSource(EnumDataSource.AddressGuid)]
    [DuplicateSearchFieldDefinition(EnumDataSourceExtension.CompanyGuid, "Name", true)]
    [DuplicateSearchFieldDefinition(EnumDataSource.AddressGuid, "Address")]
    [DuplicateSearchFieldDefinition(EnumDataSource.AddressGuid, "PostalCode", skipPhoneticTranslation: true)]
    [DuplicateSearchRelationDefinition(EnumDataSourceExtension.CompanyGuid, EnumDataSource.AddressGuid, EnumRelationType.PrimaryAddressGuid)]

Erläuterungen:

  • MainDataSource Dieser Adapter prüft Datensätze des Typs „Firma“
  • SubDataSource Es gibt zu prüfende Unterdatensätze des Type „Address“
  • FieldDefinition Das Feld „Name“ der Firma muss geprüft werden, die ganze Prüfung braucht aber überhaupt nicht erst zu beginnen, so lange dieses Feld keinen Wert beinhaltet.
  • FieldDefinition Des Weiteren gehört das Feld „Address“ auf Unterdatensätzen des Typs „Address“ zur Prüfung, genauso wie das Feld „PostalCode“. Für letzteres sollen keine Phonetiken angelegt werden, da Zahlen nicht abgebildet werden können.
  • RelationDefinition Das Betrifft nur Anschriften, die mit der Firma über die Relation „PrimaryAddress“ verknüpft sind.

Szenario 2 (aktuelle Situation in BA.CRM):
Anschriften von Firmen liegen in Teil-Datensätzen vor, welche logisch Teil des Hauptdatensatzes sind. Die Primäranschrift wird durch Auswahl eines entsprechenden Wertes im Teil-Datensatz definiert.

Die Attribute hierfür müssen wie folgt angegeben werden:
bc. [DuplicateSearchMainDataSource(EnumDataSourceExtension.CompanyGuid)] [DuplicateSearchFieldDefinition(EnumDataSourceExtension.CompanyGuid, “Name”, true)] [DuplicateSearchSubRecordFieldDefinition(EnumDataSourceExtension.CompanyGuid, “Addresses”, “Address”, uniqueKey: EnumAddressTypes.MainAddressGuid)] [DuplicateSearchSubRecordFieldDefinition(EnumDataSourceExtension.CompanyGuid, “Addresses”, “PostalCode”, uniqueKey: EnumAddressTypes.MainAddressGuid, skipPhoneticTranslation: true)]

Erläuterungen:

  • MainDataSource Dieser Adapter prüft Datensätze des Typs „Firma“
  • FieldDefinition Das Feld „Name“ der Firma muss geprüft werden, die ganze Prüfung braucht aber überhaupt nicht erst zu beginnen, so lange dieses Feld keinen Wert beinhaltet.
  • SubRecordFieldDefinition Des Weiteren gehören die Felder „Address“ und „PostalCode“ in Teildatensätzen des Properties „Addresses“ zur Prüfung. Der eindeutige Schlüssel des zu prüfenden Teildatensatzes muss „MainAddress“ lauten. Für das Feld „PostalCode“ sollen keine Phonetiken angelegt werden, da Zahlen nicht abgebildet werden können.