• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

HicServices / RDMP / 6237307473

19 Sep 2023 04:02PM UTC coverage: 57.015% (-0.4%) from 57.44%
6237307473

push

github

web-flow
Feature/rc4 (#1570)

* Syntax tidying
* Dependency updates
* Event handling singletons (ThrowImmediately and co)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: James A Sutherland <>
Co-authored-by: James Friel <jfriel001@dundee.ac.uk>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

10734 of 20259 branches covered (0.0%)

Branch coverage included in aggregate %.

5922 of 5922 new or added lines in 565 files covered. (100.0%)

30687 of 52390 relevant lines covered (58.57%)

7361.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.07
/Rdmp.Core/DataExport/Data/ExtractableCohort.cs
1
// Copyright (c) The University of Dundee 2018-2019
2
// This file is part of the Research Data Management Platform (RDMP).
3
// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4
// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5
// You should have received a copy of the GNU General Public License along with RDMP. If not, see <https://www.gnu.org/licenses/>.
6

7
using System;
8
using System.Collections.Generic;
9
using System.Data;
10
using System.Data.Common;
11
using System.Diagnostics;
12
using System.Linq;
13
using FAnsi.Discovery;
14
using FAnsi.Discovery.QuerySyntax;
15
using Rdmp.Core.Curation.Data;
16
using Rdmp.Core.MapsDirectlyToDatabaseTable;
17
using Rdmp.Core.MapsDirectlyToDatabaseTable.Attributes;
18
using Rdmp.Core.MapsDirectlyToDatabaseTable.Injection;
19
using Rdmp.Core.Repositories;
20
using Rdmp.Core.ReusableLibraryCode;
21
using Rdmp.Core.ReusableLibraryCode.Checks;
22
using Rdmp.Core.ReusableLibraryCode.Progress;
23

24
namespace Rdmp.Core.DataExport.Data;
25

26
/// <inheritdoc cref="IExtractableCohort"/>
27
public class ExtractableCohort : DatabaseEntity, IExtractableCohort, IInjectKnown<IExternalCohortDefinitionData>,
28
    IInjectKnown<ExternalCohortTable>, ICustomSearchString
29
{
30
    /// <summary>
31
    /// Logging entry in the RDMP central relational log under which to record all activities that relate to creating cohorts
32
    /// </summary>
33
    public const string CohortLoggingTask = "CohortManagement";
34

35
    #region Database Properties
36

37
    private int _externalCohortTable_ID;
38
    private string _overrideReleaseIdentifierSQL;
39
    private string _auditLog;
40
    private bool _isDeprecated;
41

42
    /// <inheritdoc/>
43
    public int ExternalCohortTable_ID
44
    {
45
        get => _externalCohortTable_ID;
570✔
46
        set => SetField(ref _externalCohortTable_ID, value);
346✔
47
    }
48

49
    /// <inheritdoc/>
50
    public string OverrideReleaseIdentifierSQL
51
    {
52
        get => _overrideReleaseIdentifierSQL;
434✔
53
        set => SetField(ref _overrideReleaseIdentifierSQL, value);
348✔
54
    }
55

56
    /// <inheritdoc/>
57
    public string AuditLog
58
    {
59
        get => _auditLog;
294✔
60
        set => SetField(ref _auditLog, value);
412✔
61
    }
62

63
    /// <inheritdoc/>
64
    public int OriginID
65
    {
66
        get => _originID;
746✔
67
        set => SetField(ref _originID, value);
346✔
68
    }
69

70
    /// <summary>
71
    /// True if the cohort has been replaced by another cohort or otherwise should not be used
72
    /// </summary>
73
    public bool IsDeprecated
74
    {
75
        get => _isDeprecated;
232✔
76
        set => SetField(ref _isDeprecated, value);
352✔
77
    }
78

79
    #endregion
80

81

82
    private int _count = -1;
344✔
83

84

85
    /// <inheritdoc/>
86
    [NoMappingToDatabase]
87
    public int Count
88
    {
89
        get
90
        {
91
            if (_count != -1)
6!
92
                return _count;
×
93
            _count = CountCohortInDatabase();
6✔
94
            return _count;
6✔
95
        }
96
    }
97

98
    private int _countDistinct = -1;
344✔
99

100
    /// <inheritdoc/>
101
    [NoMappingToDatabase]
102
    public int CountDistinct
103
    {
104
        get
105
        {
106
            if (_countDistinct != -1)
56✔
107
                return _countDistinct;
2✔
108
            _countDistinct = GetCountDistinctFromDatabase();
54✔
109
            return _countDistinct;
54✔
110
        }
111
    }
112

113
    private Dictionary<string, string> _releaseToPrivateKeyDictionary;
114

115
    #region Relationships
116

117
    /// <inheritdoc cref="ExternalCohortTable_ID"/>
118
    [NoMappingToDatabase]
119
    public IExternalCohortTable ExternalCohortTable => _knownExternalCohortTable.Value;
1,502✔
120

121
    #endregion
122

123
    /// <summary>
124
    /// Alias field, returns <see cref="INamed.Name"/>
125
    /// </summary>
126
    [NoMappingToDatabase]
127
    [UsefulProperty(DisplayName = "Source")]
128
    public string Source => ExternalCohortTable.Name;
×
129

130
    /// <summary>
131
    /// Fetches and returns the project number listed in the remote cohort database for this cohort (results are cached)
132
    /// </summary>
133
    [NoMappingToDatabase]
134
    [UsefulProperty(DisplayName = "P")]
135
    public int ExternalProjectNumber
136
    {
137
        get { return (int?)GetFromCacheData(x => x.ExternalProjectNumber) ?? -1; }
8!
138
    }
139

140
    /// <summary>
141
    /// Fetches and returns the version number listed in the remote cohort database for this cohort (results are cached)
142
    /// </summary>
143
    [NoMappingToDatabase]
144
    [UsefulProperty(DisplayName = "V")]
145
    public int ExternalVersion
146
    {
147
        get { return (int?)GetFromCacheData(x => x.ExternalVersion) ?? -1; }
×
148
    }
149

150
    private object GetFromCacheData(Func<IExternalCohortDefinitionData, object> func)
151
    {
152
        if (_broken)
20!
153
            return null;
×
154

155
        try
156
        {
157
            var v = _cacheData.Value;
20✔
158
            return func(v);
20✔
159
        }
160
        catch (Exception)
×
161
        {
162
            _broken = true;
×
163
            _cacheData = new Lazy<IExternalCohortDefinitionData>((IExternalCohortDefinitionData)null);
×
164
        }
×
165

166
        return null;
×
167
    }
20✔
168

169
    public ExtractableCohort()
2✔
170
    {
171
        ClearAllInjections();
2✔
172
    }
2✔
173

174
    internal ExtractableCohort(IDataExportRepository repository, DbDataReader r)
175
        : base(repository, r)
270✔
176
    {
177
        OverrideReleaseIdentifierSQL = r["OverrideReleaseIdentifierSQL"] as string;
270✔
178
        OriginID = Convert.ToInt32(r["OriginID"]);
270✔
179
        ExternalCohortTable_ID = Convert.ToInt32(r["ExternalCohortTable_ID"]);
270✔
180
        AuditLog = r["AuditLog"] as string;
270✔
181
        IsDeprecated = (bool)r["IsDeprecated"];
270✔
182

183
        ClearAllInjections();
270✔
184
    }
270✔
185

186
    /// <inheritdoc/>
187
    public IExternalCohortDefinitionData GetExternalData(int timeoutInSeconds = -1)
188
    {
189
        var db = ExternalCohortTable.Discover();
42✔
190

191
        var syntax = db.Server.GetQuerySyntaxHelper();
42✔
192

193
        var sql =
42✔
194
            $@"Select
42✔
195
{syntax.EnsureWrapped("projectNumber")},
42✔
196
{syntax.EnsureWrapped("description")},
42✔
197
{syntax.EnsureWrapped("version")},
42✔
198
{syntax.EnsureWrapped("dtCreated")}
42✔
199
from {ExternalCohortTable.DefinitionTableName}
42✔
200
where
42✔
201
    {syntax.EnsureWrapped("id")} = {OriginID}";
42✔
202

203
        if (timeoutInSeconds != -1) db.Server.TestConnection(timeoutInSeconds * 1000);
50✔
204

205
        using var con = db.Server.GetConnection();
42✔
206
        con.Open();
42✔
207
        using var getDescription = db.Server.GetCommand(sql, con);
42✔
208
        if (timeoutInSeconds != -1)
42✔
209
            getDescription.CommandTimeout = timeoutInSeconds;
8✔
210

211
        using var r = getDescription.ExecuteReader();
42✔
212
        return r.Read()
42!
213
            ? new ExternalCohortDefinitionData(r, ExternalCohortTable.Name)
42✔
214
            : ExternalCohortDefinitionData.Orphan;
42✔
215
    }
42✔
216

217

218
    private Lazy<IExternalCohortDefinitionData> _cacheData;
219
    private Lazy<IExternalCohortTable> _knownExternalCohortTable;
220
    private int _originID;
221

222
    /// <summary>
223
    /// Creates a new cohort reference in the data export database.  This must resolve (via <paramref name="originalId"/>) to
224
    /// a row in the external cohort database (<paramref name="externalSource"/>).
225
    /// </summary>
226
    /// <param name="repository"></param>
227
    /// <param name="externalSource"></param>
228
    /// <param name="originalId"></param>
229
    public ExtractableCohort(IDataExportRepository repository, ExternalCohortTable externalSource, int originalId)
72✔
230
    {
231
        Repository = repository;
72✔
232

233
        if (!externalSource.IDExistsInCohortTable(originalId))
72✔
234
            throw new Exception(
2✔
235
                $"ID {originalId} does not exist in Cohort Definitions (Referential Integrity Problem)");
2✔
236

237
        Repository.InsertAndHydrate(this, new Dictionary<string, object>
70✔
238
        {
70✔
239
            { "OriginID", originalId },
70✔
240
            { "ExternalCohortTable_ID", externalSource.ID }
70✔
241
        });
70✔
242

243
        ClearAllInjections();
70✔
244
    }
70✔
245

246
    /// <summary>
247
    /// Returns the external description of the cohort (held in the remote cohort database <see cref="ExternalCohortTable"/>) or
248
    /// "Broken Cohort" if that database is unreachable
249
    /// </summary>
250
    /// <returns></returns>
251
    public override string ToString()
252
    {
253
        return GetFromCacheData(x => x.ExternalDescription) as string ?? "Broken Cohort";
32!
254
    }
255

256
    /// <inheritdoc/>
257
    public string GetSearchString() => $"{ToString()} {ExternalProjectNumber} {ExternalVersion}";
×
258

259
    private IQuerySyntaxHelper _cachedQuerySyntaxHelper;
260

261
    /// <inheritdoc/>
262
    public IQuerySyntaxHelper GetQuerySyntaxHelper()
263
    {
264
        return _cachedQuerySyntaxHelper ??= ExternalCohortTable.GetQuerySyntaxHelper();
478✔
265
    }
266

267
    #region Stuff for executing the actual queries described by this class (generating cohorts etc)
268

269
    /// <inheritdoc/>
270
    public DataTable FetchEntireCohort()
271
    {
272
        var ect = ExternalCohortTable;
8✔
273

274
        var cohortTable = ect.DiscoverCohortTable();
8✔
275

276
        using var con = cohortTable.Database.Server.GetConnection();
8✔
277
        con.Open();
8✔
278
        var sql = $"SELECT DISTINCT * FROM {cohortTable.GetFullyQualifiedName()} WHERE {WhereSQL()}";
8✔
279

280
        var da = cohortTable.Database.Server.GetDataAdapter(sql, con);
8✔
281
        var dtReturn = new DataTable();
8✔
282
        dtReturn.BeginLoadData();
8✔
283
        da.Fill(dtReturn);
8✔
284
        dtReturn.EndLoadData();
8✔
285

286
        dtReturn.TableName = cohortTable.GetRuntimeName();
8✔
287

288
        return dtReturn;
8✔
289
    }
8✔
290

291
    /// <inheritdoc/>
292
    public string WhereSQL()
293
    {
294
        var ect = ExternalCohortTable;
192✔
295
        var syntax = ect.GetQuerySyntaxHelper();
192✔
296

297
        return
192!
298
            $"{syntax.EnsureFullyQualified(syntax.GetRuntimeName(ect.Database ?? string.Empty), /* no schema*/ null, syntax.GetRuntimeName(ect.TableName ?? string.Empty), syntax.GetRuntimeName(ect.DefinitionTableForeignKeyField ?? string.Empty))}={OriginID}";
192✔
299
    }
300

301
    private int CountCohortInDatabase()
302
    {
303
        var ect = ExternalCohortTable;
6✔
304

305
        var db = ect.Discover();
6✔
306
        using var con = db.Server.GetConnection();
6✔
307
        con.Open();
6✔
308

309
        using var cmd = db.Server.GetCommand($"SELECT count(*) FROM {ect.TableName} WHERE {WhereSQL()}", con);
6✔
310
        return Convert.ToInt32(cmd.ExecuteScalar());
6✔
311
    }
6✔
312

313
    /// <inheritdoc/>
314
    public int GetCountDistinctFromDatabase(int timeout = -1)
315
    {
316
        var syntax = GetQuerySyntaxHelper();
62✔
317

318
        return Convert.ToInt32(ExecuteScalar(
62✔
319
            $"SELECT count(DISTINCT {syntax.EnsureWrapped(GetReleaseIdentifier(true))}) FROM {ExternalCohortTable.TableName} WHERE {WhereSQL()}",
62✔
320
            timeout));
62✔
321
    }
322

323
    private object ExecuteScalar(string sql, int timeout = -1)
324
    {
325
        var ect = ExternalCohortTable;
62✔
326

327
        var db = ect.Discover();
62✔
328
        using var con = db.Server.GetConnection();
62✔
329
        con.Open();
62✔
330

331
        using var cmd = db.Server.GetCommand(sql, con);
62✔
332
        if (timeout != -1)
62✔
333
            cmd.CommandTimeout = timeout;
8✔
334

335
        return cmd.ExecuteScalar();
62✔
336
    }
62✔
337

338
    #endregion
339

340
    /// <summary>
341
    /// Returns details of all cohorts held in <paramref name="externalSource"/> (that have at least one identifier mapping).
342
    /// </summary>
343
    /// <param name="externalSource"></param>
344
    /// <returns></returns>
345
    public static IEnumerable<CohortDefinition> GetImportableCohortDefinitions(ExternalCohortTable externalSource)
346
    {
347
        using var dt = GetImportableCohortDefinitionsTable(externalSource,
50✔
348
            out var displayMemberName,
50✔
349
            out var valueMemberName,
50✔
350
            out var versionMemberName,
50✔
351
            out var projectNumberMemberName);
50✔
352
        foreach (DataRow r in dt.Rows)
104✔
353
            yield return
2✔
354
                new CohortDefinition(
2✔
355
                    Convert.ToInt32(r[valueMemberName]),
2✔
356
                    r[displayMemberName].ToString(),
2✔
357
                    Convert.ToInt32(r[versionMemberName]),
2✔
358
                    Convert.ToInt32(r[projectNumberMemberName])
2✔
359
                    , externalSource);
2✔
360
    }
50✔
361

362
    /// <summary>
363
    /// Returns the remote DataTable row held in <paramref name="externalSource"/> that describes all cohorts held in it (that have at least one identifier mapping).
364
    /// </summary>
365
    /// <param name="externalSource"></param>
366
    /// <param name="displayMemberName"></param>
367
    /// <param name="valueMemberName"></param>
368
    /// <param name="versionMemberName"></param>
369
    /// <param name="projectNumberMemberName"></param>
370
    /// <returns></returns>
371
    public static DataTable GetImportableCohortDefinitionsTable(ExternalCohortTable externalSource,
372
        out string displayMemberName, out string valueMemberName, out string versionMemberName,
373
        out string projectNumberMemberName)
374
    {
375
        var server = externalSource.Discover().Server;
50✔
376
        var syntax = server.GetQuerySyntaxHelper();
50✔
377

378
        using var con = server.GetConnection();
50✔
379
        con.Open();
50✔
380
        var sql =
50✔
381
            $@"Select 
50✔
382
{syntax.EnsureWrapped("description")},
50✔
383
{syntax.EnsureWrapped("id")},
50✔
384
{syntax.EnsureWrapped("version")},
50✔
385
{syntax.EnsureWrapped("projectNumber")}
50✔
386
from {externalSource.DefinitionTableName}
50✔
387
where
50✔
388
    exists (Select 1 from {externalSource.TableName} WHERE {externalSource.DefinitionTableForeignKeyField}=id)";
50✔
389

390
        using var da = server.GetDataAdapter(sql, con);
50✔
391
        displayMemberName = "description";
50✔
392
        valueMemberName = "id";
50✔
393
        versionMemberName = "version";
50✔
394
        projectNumberMemberName = "projectNumber";
50✔
395

396
        var toReturn = new DataTable();
50✔
397
        toReturn.BeginLoadData();
50✔
398
        da.Fill(toReturn);
50✔
399
        toReturn.EndLoadData();
50✔
400
        return toReturn;
50✔
401
    }
50✔
402

403
    /// <inheritdoc/>
404
    public string GetReleaseIdentifier(bool runtimeName = false)
405
    {
406
        //respect override
407
        var toReturn = string.IsNullOrWhiteSpace(OverrideReleaseIdentifierSQL)
212✔
408
            ? ExternalCohortTable.ReleaseIdentifierField
212✔
409
            : OverrideReleaseIdentifierSQL;
212✔
410

411
        if (toReturn.Equals(ExternalCohortTable.PrivateIdentifierField))
212✔
412
            ThrowImmediatelyCheckNotifier.Quiet
4✔
413
                .OnCheckPerformed(new CheckEventArgs(ErrorCodes.ExtractionIsIdentifiable));
4✔
414

415
        var syntaxHelper = GetQuerySyntaxHelper();
210✔
416

417
        if (syntaxHelper.GetRuntimeName(toReturn)
210✔
418
            .Equals(syntaxHelper.GetRuntimeName(ExternalCohortTable.PrivateIdentifierField)))
210✔
419
            ThrowImmediatelyCheckNotifier.Quiet
2✔
420
                .OnCheckPerformed(new CheckEventArgs(ErrorCodes.ExtractionIsIdentifiable));
2✔
421

422
        return runtimeName ? GetQuerySyntaxHelper().GetRuntimeName(toReturn) : toReturn;
210✔
423
    }
424

425
    /// <inheritdoc/>
426
    public string GetPrivateIdentifier(bool runtimeName = false) => runtimeName
24✔
427
        ? GetQuerySyntaxHelper().GetRuntimeName(ExternalCohortTable.PrivateIdentifierField)
24✔
428
        : ExternalCohortTable.PrivateIdentifierField;
24✔
429

430
    /// <inheritdoc/>
431
    public string GetPrivateIdentifierDataType() => ExternalCohortTable.DiscoverPrivateIdentifier().DataType.SQLType;
×
432

433
    /// <inheritdoc/>
434
    public string GetReleaseIdentifierDataType() => ExternalCohortTable.DiscoverReleaseIdentifier().DataType.SQLType;
2✔
435

436

437
    /// <inheritdoc/>
438
    public DiscoveredDatabase GetDatabaseServer() => ExternalCohortTable.Discover();
×
439

440
    //these need to be private since ReverseAnonymiseDataTable will likely be called in batch
441
    private int _reverseAnonymiseProgressFetchingMap;
442
    private int _reverseAnonymiseProgressReversing;
443

444
    /// <summary>
445
    /// Indicates whether the database described in ExternalCohortTable is unreachable or if the cohort has since been deleted etc.
446
    /// </summary>
447
    private bool _broken;
448

449
    /// <inheritdoc/>
450
    public void ReverseAnonymiseDataTable(DataTable toProcess, IDataLoadEventListener listener, bool allowCaching)
451
    {
452
        var haveWarnedAboutTop1AlreadyCount = 10;
4✔
453

454
        var syntax = ExternalCohortTable.GetQuerySyntaxHelper();
4✔
455

456
        var privateIdentifier = syntax.GetRuntimeName(GetPrivateIdentifier());
4✔
457
        var releaseIdentifier = syntax.GetRuntimeName(GetReleaseIdentifier());
4✔
458

459
        //if we don't want to support caching or there is no cached value yet
460
        if (!allowCaching || _releaseToPrivateKeyDictionary == null)
4✔
461
        {
462
            var map = FetchEntireCohort();
4✔
463

464

465
            var sw = new Stopwatch();
4✔
466
            sw.Start();
4✔
467
            //dictionary of released values (for the cohort) back to private values
468
            _releaseToPrivateKeyDictionary = new Dictionary<string, string>();
4✔
469
            foreach (DataRow r in map.Rows)
16✔
470
            {
471
                if (_releaseToPrivateKeyDictionary.Keys.Contains(r[releaseIdentifier]))
4!
472
                {
473
                    if (haveWarnedAboutTop1AlreadyCount > 0)
×
474
                    {
475
                        haveWarnedAboutTop1AlreadyCount--;
×
476
                        listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning,
×
477
                            $"Top 1-ing will occur for release identifier {r[releaseIdentifier]} because it maps to multiple private identifiers"));
×
478
                    }
479
                    else
480
                    {
481
                        if (haveWarnedAboutTop1AlreadyCount == 0)
×
482
                        {
483
                            haveWarnedAboutTop1AlreadyCount = -1;
×
484
                            listener.OnNotify(this,
×
485
                                new NotifyEventArgs(ProgressEventType.Warning,
×
486
                                    "Top 1-ing error message disabled due to flood of messages"));
×
487
                        }
488
                    }
489
                }
490
                else
491
                {
492
                    _releaseToPrivateKeyDictionary.Add(r[releaseIdentifier].ToString().Trim(),
4✔
493
                        r[privateIdentifier].ToString().Trim());
4✔
494
                }
495

496
                _reverseAnonymiseProgressFetchingMap++;
4✔
497

498
                if (_reverseAnonymiseProgressFetchingMap % 500 == 0)
4!
499
                    listener.OnProgress(this,
×
500
                        new ProgressEventArgs("Assembling Release Map Dictionary",
×
501
                            new ProgressMeasurement(_reverseAnonymiseProgressFetchingMap, ProgressType.Records),
×
502
                            sw.Elapsed));
×
503
            }
504

505
            listener.OnProgress(this,
4✔
506
                new ProgressEventArgs("Assembling Release Map Dictionary",
4✔
507
                    new ProgressMeasurement(_reverseAnonymiseProgressFetchingMap, ProgressType.Records), sw.Elapsed));
4✔
508
        }
509

510
        var nullsFound = 0;
4✔
511
        var substitutions = 0;
4✔
512

513
        var sw2 = new Stopwatch();
4✔
514
        sw2.Start();
4✔
515

516
        //fix values
517
        toProcess.BeginLoadData();
4✔
518
        foreach (DataRow row in toProcess.Rows)
24✔
519
            try
520
            {
521
                var value = row[releaseIdentifier];
8✔
522

523
                if (value == null || value == DBNull.Value)
8!
524
                {
525
                    nullsFound++;
×
526
                    continue;
×
527
                }
528

529
                row[releaseIdentifier] =
8✔
530
                    _releaseToPrivateKeyDictionary[value.ToString().Trim()]
8✔
531
                        .Trim(); //swap release value for private value (reversing the anonymisation)
8✔
532
                substitutions++;
8✔
533

534
                _reverseAnonymiseProgressReversing++;
8✔
535

536
                if (_reverseAnonymiseProgressReversing % 500 == 0)
8!
537
                    listener.OnProgress(this,
×
538
                        new ProgressEventArgs("Substituting Release Identifiers For Private Identifiers",
×
539
                            new ProgressMeasurement(_reverseAnonymiseProgressReversing, ProgressType.Records),
×
540
                            sw2.Elapsed));
×
541
            }
8✔
542
            catch (KeyNotFoundException e)
×
543
            {
544
                throw new Exception(
×
545
                    $"Could not find private identifier ({privateIdentifier}) for the release identifier ({releaseIdentifier}) with value '{row[releaseIdentifier]}' in cohort with cohortDefinitionID {OriginID}",
×
546
                    e);
×
547
            }
548

549
        //final value
550
        listener.OnProgress(this,
4✔
551
            new ProgressEventArgs("Substituting Release Identifiers For Private Identifiers",
4✔
552
                new ProgressMeasurement(_reverseAnonymiseProgressReversing, ProgressType.Records), sw2.Elapsed));
4✔
553

554
        if (nullsFound > 0)
4!
555
            listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning,
×
556
                $"Found {nullsFound} null release identifiers amongst the {toProcess.Rows.Count} rows of the input data table (on which we were attempting to reverse anonymise)"));
×
557

558
        listener.OnNotify(this, new NotifyEventArgs(
4!
559
            substitutions > 0 ? ProgressEventType.Information : ProgressEventType.Error,
4✔
560
            $"Substituted {substitutions} release identifiers for private identifiers in input data table (input data table contained {toProcess.Rows.Count} rows)"));
4✔
561

562
        toProcess.Columns[releaseIdentifier].ColumnName = privateIdentifier;
4✔
563

564
        toProcess.EndLoadData();
4✔
565
    }
4✔
566

567
    /// <summary>
568
    /// Appends the <paramref name="s"/> to the <see cref="AuditLog"/> prefixed by the DateTime and Username of the caller and then saves to database
569
    /// </summary>
570
    /// <param name="s"></param>
571
    public void AppendToAuditLog(string s)
572
    {
573
        AuditLog ??= "";
36✔
574

575
        AuditLog += $"{Environment.NewLine}{DateTime.Now} {Environment.UserName} {s}";
36✔
576
        SaveToDatabase();
36✔
577
    }
36✔
578

579
    /// <inheritdoc/>
580
    public void InjectKnown(IExternalCohortDefinitionData instance)
581
    {
582
        _broken = instance == null;
44✔
583

584
        _cacheData = new Lazy<IExternalCohortDefinitionData>(instance);
44✔
585
    }
44✔
586

587
    /// <inheritdoc/>
588
    public void InjectKnown(ExternalCohortTable instance)
589
    {
590
        _knownExternalCohortTable = new Lazy<IExternalCohortTable>(instance);
44✔
591
    }
44✔
592

593
    /// <inheritdoc/>
594
    public void ClearAllInjections()
595
    {
596
        _cacheData = new Lazy<IExternalCohortDefinitionData>(() => GetExternalData());
370✔
597
        _knownExternalCohortTable =
350✔
598
            new Lazy<IExternalCohortTable>(() => Repository.GetObjectByID<ExternalCohortTable>(ExternalCohortTable_ID));
496✔
599
        _broken = false;
350✔
600
    }
350✔
601

602
    /// <inheritdoc/>
603
    public IHasDependencies[] GetObjectsThisDependsOn()
604
    {
605
        return new[] { ExternalCohortTable };
×
606
    }
607

608
    /// <inheritdoc/>
609
    public IHasDependencies[] GetObjectsDependingOnThis() =>
610
        Repository.GetAllObjectsWhere<ExtractionConfiguration>("Cohort_ID ", ID);
×
611
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc