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

HicServices / RDMP / 7194961165

13 Dec 2023 12:07PM UTC coverage: 56.776% (-0.2%) from 57.013%
7194961165

push

github

web-flow
Merge Latest Release into main (#1702)

* Bump YamlDotNet from 13.3.1 to 13.4.0

Bumps [YamlDotNet](https://github.com/aaubry/YamlDotNet) from 13.3.1 to 13.4.0.
- [Release notes](https://github.com/aaubry/YamlDotNet/releases)
- [Commits](https://github.com/aaubry/YamlDotNet/compare/v13.3.1...v13.4.0)

---
updated-dependencies:
- dependency-name: YamlDotNet
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump shogo82148/actions-setup-perl from 1.23.1 to 1.24.1

Bumps [shogo82148/actions-setup-perl](https://github.com/shogo82148/actions-setup-perl) from 1.23.1 to 1.24.1.
- [Release notes](https://github.com/shogo82148/actions-setup-perl/releases)
- [Commits](https://github.com/shogo82148/actions-setup-perl/compare/v1.23.1...v1.24.1)

---
updated-dependencies:
- dependency-name: shogo82148/actions-setup-perl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix checkbox issue

* improve confirmation text (#1639)

* improve confirmation text
* Loop tidyup, use var where possible

---------

Co-authored-by: jas88 <j.a.sutherland@dundee.ac.uk>

* correct typo in create logging sql (#1640)

* Feature/ci codescan (#1641)

* Move SecurityCodescan.VS2019 to run on Github CI alone, integrate results with CodeQL
* Remove SecurityCodescan from Packages.md, no longer used via Nuget

---------

Co-authored-by: James A Sutherland <j@sutherland.pw>

* hide source control when not available

* Remove old Plugin object bits, tidy up (#1636)

* Remove old Plugin object bits, tidy up

* Purge remaining bits of AllExpiredPluginsNode

* Fix plugin display name in tree

* Update CreateNewDataExtractionProjectUI.cs

Casting fix

* Feature/rdmp42 delete plugins (#1642)

* add ui plugin delete functionality

* Warning and inherita... (continued)

10722 of 20351 branches covered (0.0%)

Branch coverage included in aggregate %.

215 of 789 new or added lines in 63 files covered. (27.25%)

39 existing lines in 16 files now uncovered.

30650 of 52518 relevant lines covered (58.36%)

7294.17 hits per line

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

79.79
/Rdmp.Core/Repositories/YamlRepository.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.IO;
10
using System.Linq;
11
using Rdmp.Core.Curation.Data;
12
using Rdmp.Core.Curation.Data.Aggregation;
13
using Rdmp.Core.Curation.Data.Cohort;
14
using Rdmp.Core.Curation.Data.Defaults;
15
using Rdmp.Core.Curation.Data.Governance;
16
using Rdmp.Core.Curation.Data.Remoting;
17
using Rdmp.Core.DataExport.Data;
18
using Rdmp.Core.MapsDirectlyToDatabaseTable;
19
using Rdmp.Core.Repositories.Managers;
20
using Rdmp.Core.ReusableLibraryCode.DataAccess;
21
using YamlDotNet.Serialization;
22

23
namespace Rdmp.Core.Repositories;
24

25
/// <summary>
26
/// Implementation of <see cref="IRepository"/> which creates objects on the file system instead of a database.
27
/// </summary>
28
public class YamlRepository : MemoryDataExportRepository
29
{
30
    private ISerializer _serializer;
31

32
    /// <summary>
33
    /// All objects that are known about by this repository
34
    /// </summary>
35
    public IReadOnlyCollection<IMapsDirectlyToDatabaseTable> AllObjects => Objects.Keys.ToList().AsReadOnly();
8✔
36

37
    public DirectoryInfo Directory { get; }
2,670✔
38

39
    private object lockFs = new();
48✔
40

41
    public YamlRepository(DirectoryInfo dir)
48✔
42
    {
43
        Directory = dir;
48✔
44

45
        if (!Directory.Exists)
48✔
46
            Directory.Create();
24✔
47

48
        // Build the serializer
49
        _serializer = CreateSerializer(GetCompatibleTypes());
48✔
50

51
        LoadObjects();
48✔
52

53
        if (File.Exists(GetEncryptionKeyPathFile()))
48!
54
            EncryptionKeyPath = File.ReadAllText(GetEncryptionKeyPathFile());
×
55

56
        // Don't create new objects with the ID of existing objects
57
        NextObjectId = Objects.IsEmpty ? 0 : Objects.Max(o => o.Key.ID);
202✔
58
    }
48✔
59

60
    /// <summary>
61
    /// 
62
    /// </summary>
63
    /// <returns></returns>
64
    public static ISerializer CreateSerializer(IEnumerable<Type> supportedTypes)
65
    {
66
        var builder = new SerializerBuilder();
52✔
67
        builder.WithTypeConverter(new VersionYamlTypeConverter());
52✔
68

69
        foreach (var type in supportedTypes)
8,096✔
70
        {
71
            var respect = TableRepository.GetPropertyInfos(type);
3,996✔
72

73
            builder = type.GetProperties().Where(prop => !respect.Contains(prop)).Aggregate(builder,
52,988✔
74
                (current, prop) => current.WithAttributeOverride(type, prop.Name, new YamlIgnoreAttribute()));
24,184✔
75
        }
76

77
        return builder.Build();
52✔
78
    }
79

80
    private void LoadObjects()
81
    {
82
        var subdirs = Directory.GetDirectories();
48✔
83
        var builder = new DeserializerBuilder();
48✔
84
        builder.WithTypeConverter(new VersionYamlTypeConverter());
48✔
85

86
        var deserializer = builder.Build();
48✔
87

88
        foreach (var t in GetCompatibleTypes().OrderBy(ObjectDependencyOrder))
7,488✔
89
        {
90
            // find the directory that contains all the YAML files e.g. MyDir/Catalogue/
91
            var typeDir = subdirs.FirstOrDefault(d => d.Name.Equals(t.Name));
76,224✔
92

93
            if (typeDir == null)
3,696✔
94
            {
95
                Directory.CreateSubdirectory(t.Name);
1,848✔
96
                continue;
1,848✔
97
            }
98

99
            lock (lockFs)
1,848✔
100
            {
101
                foreach (var yaml in typeDir.EnumerateFiles("*.yaml"))
4,004✔
102
                    try
103
                    {
104
                        var obj = (IMapsDirectlyToDatabaseTable)deserializer.Deserialize(
154✔
105
                            File.ReadAllText(yaml.FullName), t);
154✔
106
                        SetRepositoryOnObject(obj);
154✔
107
                        Objects.TryAdd(obj, 0);
154✔
108
                    }
154✔
109
                    catch (Exception ex)
×
110
                    {
111
                        throw new Exception($"Error loading object in file {yaml.FullName}", ex);
×
112
                    }
113
            }
114
        }
115

116
        LoadDefaults();
48✔
117

118
        LoadDataExportProperties();
48✔
119

120
        LoadCredentialsDictionary();
48✔
121

122
        PackageDictionary = Load<IExtractableDataSetPackage, IExtractableDataSet>(nameof(PackageDictionary)) ??
48!
123
                            PackageDictionary;
48✔
124

125
        GovernanceCoverage = Load<GovernancePeriod, ICatalogue>(nameof(GovernanceCoverage)) ?? GovernanceCoverage;
48!
126

127
        ForcedJoins = Load<AggregateConfiguration, ITableInfo>(nameof(ForcedJoins)) ?? ForcedJoins;
48!
128

129
        LoadCohortContainerContents();
48✔
130

131
        LoadWhereSubContainers();
48✔
132
    }
48✔
133

134
    private int ObjectDependencyOrder(Type arg)
135
    {
136
        // Load Plugin objects before dependent children
137
        if (arg == typeof(Plugin))
3,696!
UNCOV
138
            return 1;
×
139

140
        return arg == typeof(LoadModuleAssembly) ? 2 : 3;
3,696!
141
    }
142

143

144
    /// <summary>
145
    /// Sets <see cref="IMapsDirectlyToDatabaseTable.Repository"/> on <paramref name="obj"/>.
146
    /// Override to also set other destination repo specific fields
147
    /// </summary>
148
    /// <param name="obj"></param>
149
    protected virtual void SetRepositoryOnObject(IMapsDirectlyToDatabaseTable obj)
150
    {
151
        obj.Repository = this;
338!
152

153
        switch (obj)
154
        {
155
            case DataAccessCredentials creds:
156
                creds.SetRepository(this);
4✔
157
                break;
4✔
158
            case ExternalDatabaseServer eds:
159
                eds.SetRepository(this);
4✔
160
                break;
4✔
161
            case ExternalCohortTable ect:
162
                ect.SetRepository(this);
×
163
                break;
×
164
            case RemoteRDMP remote:
165
                remote.SetRepository(this);
×
166
                break;
×
167
            case ConcreteContainer container:
168
                container.SetManager(this);
16✔
169
                break;
170
        }
171
    }
16✔
172

173
    public override void InsertAndHydrate<T>(T toCreate, Dictionary<string, object> constructorParameters)
174
    {
175
        base.InsertAndHydrate(toCreate, constructorParameters);
134✔
176

177
        // put it on disk
178
        lock (lockFs)
134✔
179
        {
180
            SaveToDatabase(toCreate);
134✔
181
        }
134✔
182
    }
134✔
183

184
    public override void DeleteFromDatabase(IMapsDirectlyToDatabaseTable oTableWrapperObject)
185
    {
186
        lock (lockFs)
6✔
187
        {
188
            base.DeleteFromDatabase(oTableWrapperObject);
6✔
189
            File.Delete(GetPath(oTableWrapperObject));
6✔
190
        }
6✔
191
    }
6✔
192

193
    public override void SaveToDatabase(IMapsDirectlyToDatabaseTable o)
194
    {
195
        base.SaveToDatabase(o);
184✔
196

197
        SetRepositoryOnObject(o);
184✔
198

199
        var yaml = _serializer.Serialize(o);
184✔
200

201
        lock (lockFs)
184✔
202
        {
203
            File.WriteAllText(GetPath(o), yaml);
184✔
204
        }
184✔
205
    }
184✔
206

207
    /// <summary>
208
    /// Returns the path on disk in which the yaml file for <paramref name="o"/> is stored
209
    /// </summary>
210
    /// <param name="o"></param>
211
    /// <returns></returns>
212
    private string GetPath(IMapsDirectlyToDatabaseTable o) =>
213
        Path.Combine(Directory.FullName, o.GetType().Name, $"{o.ID}.yaml");
190✔
214

215
    public override void DeleteEncryptionKeyPath()
216
    {
217
        base.DeleteEncryptionKeyPath();
×
218

219
        if (File.Exists(GetEncryptionKeyPathFile()))
×
220
            File.Delete(GetEncryptionKeyPathFile());
×
221
    }
×
222

223
    public override void SetEncryptionKeyPath(string fullName)
224
    {
225
        base.SetEncryptionKeyPath(fullName);
×
226

227
        // if setting it to null
228
        if (string.IsNullOrWhiteSpace(fullName))
×
229
        {
230
            // delete the file on disk
231
            if (File.Exists(GetEncryptionKeyPathFile()))
×
232
                File.Delete(GetEncryptionKeyPathFile());
×
233
        }
234
        else
235
        {
236
            File.WriteAllText(GetEncryptionKeyPathFile(), fullName);
×
237
        }
238
    }
×
239

240
    private string GetEncryptionKeyPathFile() => Path.Combine(Directory.FullName, "EncryptionKeyPath");
48✔
241

242
    #region Server Defaults Persistence
243

244
    private string GetDefaultsFile() => Path.Combine(Directory.FullName, "Defaults.yaml");
50✔
245

246
    public override void SetDefault(PermissableDefaults toChange, IExternalDatabaseServer externalDatabaseServer)
247
    {
248
        base.SetDefault(toChange, externalDatabaseServer);
2✔
249

250
        SaveDefaults();
2✔
251
    }
2✔
252

253
    private void SaveDefaults()
254
    {
255
        var serializer = new Serializer();
2✔
256

257
        // save the default and the ID
258
        File.WriteAllText(GetDefaultsFile(),
2✔
259
            serializer.Serialize(Defaults.ToDictionary(k => k.Key, v => v.Value?.ID ?? 0)));
34✔
260
    }
2✔
261

262
    public void LoadDefaults()
263
    {
264
        var deserializer = new Deserializer();
48✔
265

266
        var defaultsFile = GetDefaultsFile();
48✔
267

268
        if (File.Exists(defaultsFile))
48✔
269
        {
270
            var yaml = File.ReadAllText(defaultsFile);
2✔
271
            var objectIds = deserializer.Deserialize<Dictionary<PermissableDefaults, int>>(yaml);
2✔
272

273
            // file exists but is empty
274
            if (objectIds == null)
2!
275
                return;
×
276

277
            Defaults = objectIds.ToDictionary(
2✔
278
                k => k.Key,
16✔
279
                v => v.Value == 0
18✔
280
                    ? null
18✔
281
                    : (IExternalDatabaseServer)GetObjectByIDIfExists<ExternalDatabaseServer>(v.Value));
18✔
282
        }
283
    }
48✔
284

285
    /// <summary>
286
    /// Returns the object referenced or null if it has been deleted on the sly (e.g. by user deleting .yaml files on disk)
287
    /// </summary>
288
    /// <typeparam name="T"></typeparam>
289
    /// <param name="id"></param>
290
    /// <returns></returns>
291
    private T GetObjectByIDIfExists<T>(int id) where T : DatabaseEntity
292
    {
293
        try
294
        {
295
            return GetObjectByID<T>(id);
6✔
296
        }
297
        catch (KeyNotFoundException)
×
298
        {
299
            return null;
×
300
        }
301
    }
6✔
302

303
    #endregion
304

305
    #region DataExportProperties Persistence
306

307
    private string GetDataExportPropertiesFile() => Path.Combine(Directory.FullName, "DataExportProperties.yaml");
50✔
308

309
    public void LoadDataExportProperties()
310
    {
311
        var deserializer = new Deserializer();
48✔
312

313
        var defaultsFile = GetDataExportPropertiesFile();
48✔
314

315
        if (File.Exists(defaultsFile))
48✔
316
        {
317
            var yaml = File.ReadAllText(defaultsFile);
2✔
318
            var props = deserializer.Deserialize<Dictionary<DataExportProperty, string>>(yaml);
2✔
319

320
            if (props != null)
2✔
321
                PropertiesDictionary = props;
2✔
322
        }
323
    }
48✔
324

325
    private void SaveDataExportProperties()
326
    {
327
        var serializer = new Serializer();
2✔
328

329
        // save the default and the ID
330
        File.WriteAllText(GetDataExportPropertiesFile(), serializer.Serialize(PropertiesDictionary));
2✔
331
    }
2✔
332

333
    public override void SetValue(DataExportProperty property, string value)
334
    {
335
        base.SetValue(property, value);
2✔
336
        SaveDataExportProperties();
2✔
337
    }
2✔
338

339
    #endregion
340

341
    public override void AddDataSetToPackage(IExtractableDataSetPackage package, IExtractableDataSet dataSet)
342
    {
343
        base.AddDataSetToPackage(package, dataSet);
2✔
344
        Save(PackageDictionary, nameof(PackageDictionary));
2✔
345
    }
2✔
346

347
    public override void RemoveDataSetFromPackage(IExtractableDataSetPackage package, IExtractableDataSet dataSet)
348
    {
349
        base.RemoveDataSetFromPackage(package, dataSet);
×
350
        Save(PackageDictionary, nameof(PackageDictionary));
×
351
    }
×
352

353

354
    public override void Link(GovernancePeriod governancePeriod, ICatalogue catalogue)
355
    {
356
        base.Link(governancePeriod, catalogue);
2✔
357
        Save(GovernanceCoverage, nameof(GovernanceCoverage));
2✔
358
    }
2✔
359

360
    public override void Unlink(GovernancePeriod governancePeriod, ICatalogue catalogue)
361
    {
362
        base.Unlink(governancePeriod, catalogue);
×
363
        Save(GovernanceCoverage, nameof(GovernanceCoverage));
×
364
    }
×
365

366
    public override void CreateLinkBetween(AggregateConfiguration configuration, ITableInfo tableInfo)
367
    {
368
        base.CreateLinkBetween(configuration, tableInfo);
2✔
369
        Save(ForcedJoins, nameof(ForcedJoins));
2✔
370
    }
2✔
371

372
    public override void BreakLinkBetween(AggregateConfiguration configuration, ITableInfo tableInfo)
373
    {
374
        base.BreakLinkBetween(configuration, tableInfo);
×
375
        Save(ForcedJoins, nameof(ForcedJoins));
×
376
    }
×
377

378
    #region Persist CredentialsDictionary
379

380
    private string GetCredentialsDictionaryFile() => Path.Combine(Directory.FullName, "CredentialsDictionary.yaml");
50✔
381

382
    public void LoadCredentialsDictionary()
383
    {
384
        var deserializer = new Deserializer();
48✔
385

386
        var file = GetCredentialsDictionaryFile();
48✔
387

388
        if (File.Exists(file))
48✔
389
        {
390
            var yaml = File.ReadAllText(file);
2✔
391

392
            var ids = deserializer.Deserialize<Dictionary<int, Dictionary<DataAccessContext, int>>>(yaml);
2✔
393

394
            // file exists but is empty
395
            if (ids == null)
2!
396
                return;
×
397

398
            CredentialsDictionary = new Dictionary<ITableInfo, Dictionary<DataAccessContext, DataAccessCredentials>>();
2✔
399

400
            foreach (var tableToCredentialUsage in ids)
8✔
401
            {
402
                var table = GetObjectByIDIfExists<TableInfo>(tableToCredentialUsage.Key);
2✔
403

404
                // TableInfo was deleted on the sly
405
                if (table == null)
2✔
406
                    continue;
407

408
                var valDictionary = new Dictionary<DataAccessContext, DataAccessCredentials>();
2✔
409
                foreach (var (usage, value) in tableToCredentialUsage.Value)
8✔
410
                {
411
                    var credential = GetObjectByIDIfExists<DataAccessCredentials>(value);
2✔
412

413
                    // Credentials can be deleted on the sly
414
                    if (credential != null) valDictionary.Add(usage, credential);
4✔
415
                }
416

417
                CredentialsDictionary.Add(table, valDictionary);
2✔
418
            }
419
        }
420
    }
48✔
421

422
    private void SaveCredentialsDictionary()
423
    {
424
        var serializer = new Serializer();
2✔
425

426
        var ids =
2✔
427
            CredentialsDictionary.ToDictionary(
2✔
428
                k => k.Key.ID,
2✔
429
                v => v.Value.ToDictionary(k => k.Key, v => v.Value.ID));
8✔
430

431
        // save the default and the ID
432
        File.WriteAllText(GetCredentialsDictionaryFile(), serializer.Serialize(ids));
2✔
433
    }
2✔
434

435
    public override void BreakAllLinksBetween(DataAccessCredentials credentials, ITableInfo tableInfo)
436
    {
437
        base.BreakAllLinksBetween(credentials, tableInfo);
×
438

439
        SaveCredentialsDictionary();
×
440
    }
×
441

442
    public override void BreakLinkBetween(DataAccessCredentials credentials, ITableInfo tableInfo,
443
        DataAccessContext context)
444
    {
445
        base.BreakLinkBetween(credentials, tableInfo, context);
×
446
        SaveCredentialsDictionary();
×
447
    }
×
448

449
    public override void CreateLinkBetween(DataAccessCredentials credentials, ITableInfo tableInfo,
450
        DataAccessContext context)
451
    {
452
        base.CreateLinkBetween(credentials, tableInfo, context);
2✔
453
        SaveCredentialsDictionary();
2✔
454
    }
2✔
455

456
    #endregion
457

458

459
    #region Persist Cohort Containers
460

461
    private void SaveCohortContainerContents(CohortAggregateContainer toSave)
462
    {
463
        var dir = Path.Combine(Directory.FullName, nameof(PersistCohortContainerContent));
4✔
464
        var file = Path.Combine(dir, $"{toSave.ID}.yaml");
4✔
465

466
        var serializer = new Serializer();
4✔
467
        var yaml = serializer.Serialize(CohortContainerContents[toSave]
4✔
468
            .Select(c => new PersistCohortContainerContent(c)).ToList());
10✔
469
        File.WriteAllText(file, yaml);
4✔
470
    }
4✔
471

472
    private void LoadCohortContainerContents()
473
    {
474
        var dir = new DirectoryInfo(Path.Combine(Directory.FullName, nameof(PersistCohortContainerContent)));
48✔
475

476
        if (!dir.Exists)
48✔
477
            dir.Create();
24✔
478

479
        var deserializer = new Deserializer();
48✔
480

481
        foreach (var f in dir.GetFiles("*.yaml").ToArray())
100✔
482
        {
483
            var id = int.Parse(Path.GetFileNameWithoutExtension(f.Name));
2✔
484

485
            var content = deserializer.Deserialize<List<PersistCohortContainerContent>>(File.ReadAllText(f.FullName));
2✔
486

487
            // file exists but is empty
488
            if (content == null)
2✔
489
                continue;
490

491
            try
492
            {
493
                CohortAggregateContainer container;
494

495
                try
496
                {
497
                    container = GetObjectByID<CohortAggregateContainer>(id);
2✔
498
                }
2✔
499
                catch (KeyNotFoundException)
×
500
                {
501
                    // The container doesn't exist anymore
502
                    f.Delete();
×
503
                    continue;
×
504
                }
505

506
                CohortContainerContents.Add(container,
2✔
507
                    new HashSet<CohortContainerContent>(content.Select(c => c.GetContent(this))));
6✔
508
            }
2✔
509
            catch (Exception ex)
×
510
            {
511
                throw new Exception($"Error reading file {f.FullName}", ex);
×
512
            }
513
        }
514
    }
48✔
515

516
    public override void Add(CohortAggregateContainer parent, AggregateConfiguration child, int order)
517
    {
518
        base.Add(parent, child, order);
2✔
519
        SaveCohortContainerContents(parent);
2✔
520
    }
2✔
521

522
    public override void Add(CohortAggregateContainer parent, CohortAggregateContainer child)
523
    {
524
        base.Add(parent, child);
2✔
525
        SaveCohortContainerContents(parent);
2✔
526
    }
2✔
527

528
    public override void Remove(CohortAggregateContainer parent, AggregateConfiguration child)
529
    {
530
        base.Remove(parent, child);
×
531
        SaveCohortContainerContents(parent);
×
532
    }
×
533

534
    public override void SetOrder(AggregateConfiguration child, int newOrder)
535
    {
536
        base.SetOrder(child, newOrder);
×
537
        SaveCohortContainerContents(child.GetCohortAggregateContainerIfAny());
×
538
    }
×
539

540
    public override void Remove(CohortAggregateContainer parent, CohortAggregateContainer child)
541
    {
542
        base.Remove(parent, child);
×
543
        SaveCohortContainerContents(parent);
×
544
    }
×
545

546
    private class PersistCohortContainerContent
547
    {
548
        public string Type { get; set; }
28✔
549
        public int ID { get; set; }
26✔
550
        public int Order { get; set; }
26✔
551

552
        public PersistCohortContainerContent()
4✔
553
        {
554
        }
4✔
555

556
        public PersistCohortContainerContent(CohortContainerContent c)
6✔
557
        {
558
            Type = c.Orderable.GetType().Name;
6✔
559
            ID = ((IMapsDirectlyToDatabaseTable)c.Orderable).ID;
6✔
560
            Order = c.Order;
6✔
561
        }
6✔
562

563

564
        public CohortContainerContent GetContent(YamlRepository repository)
565
        {
566
            if (Type.Equals(nameof(AggregateConfiguration)))
4✔
567
                return new CohortContainerContent(repository.GetObjectByID<AggregateConfiguration>(ID), Order);
2✔
568

569
            return Type.Equals(nameof(CohortAggregateContainer))
2!
570
                ? new CohortContainerContent(repository.GetObjectByID<CohortAggregateContainer>(ID), Order)
2✔
571
                : throw new Exception($"Unexpected IOrderable Type name '{Type}'");
2✔
572
        }
573
    }
574

575
    #endregion
576

577
    public override void MakeIntoAnOrphan(IContainer container)
578
    {
579
        base.MakeIntoAnOrphan(container);
4✔
580
        SaveWhereSubContainers();
4✔
581
    }
4✔
582

583
    public override void AddSubContainer(IContainer parent, IContainer child)
584
    {
585
        base.AddSubContainer(parent, child);
4✔
586
        SaveWhereSubContainers();
4✔
587
    }
4✔
588

589
    private void SaveWhereSubContainers()
590
    {
591
        Save(WhereSubContainers.Where(kvp => kvp.Key is FilterContainer)
16✔
592
            .ToDictionary(
8✔
593
                k => k.Key,
×
594
                v => v.Value), "ExtractionFilters");
8✔
595

596
        Save(WhereSubContainers.Where(kvp => kvp.Key is AggregateFilterContainer)
16✔
597
            .ToDictionary(
8✔
598
                k => k.Key,
8✔
599
                v => v.Value), "AggregateFilters");
16✔
600
    }
8✔
601

602
    public override string ToString() => $"{{YamlRepository {Directory.FullName}}}";
×
603

604
    private void LoadWhereSubContainers()
605
    {
606
        foreach (var c in Load<FilterContainer, FilterContainer>("ExtractionFilters") ??
96!
607
                          new Dictionary<FilterContainer, HashSet<FilterContainer>>())
48✔
608
            WhereSubContainers.Add(c.Key, new HashSet<IContainer>(c.Value));
×
609
        foreach (var c in Load<AggregateFilterContainer, AggregateFilterContainer>("AggregateFilters") ??
104!
610
                          new Dictionary<AggregateFilterContainer, HashSet<AggregateFilterContainer>>())
48✔
611
            WhereSubContainers.Add(c.Key, new HashSet<IContainer>(c.Value));
4✔
612
    }
48✔
613

614
    private Dictionary<T, HashSet<T2>> Load<T, T2>(string filenameWithoutSuffix)
615
        where T : IMapsDirectlyToDatabaseTable
616
        where T2 : IMapsDirectlyToDatabaseTable
617
    {
618
        var deserializer = new Deserializer();
240✔
619

620
        var file = Path.Combine(Directory.FullName, $"{filenameWithoutSuffix}.yaml");
240✔
621

622
        if (File.Exists(file))
240✔
623
        {
624
            var yaml = File.ReadAllText(file);
18✔
625

626
            var dictionary = new Dictionary<T, HashSet<T2>>();
18✔
627

628
            var dict = deserializer.Deserialize<Dictionary<int, List<int>>>(yaml);
18✔
629

630
            //file exists but is empty
631
            if (dict == null)
18!
632
                return null;
×
633

634
            foreach (var ids in dict)
60✔
635
                try
636
                {
637
                    var key = GetObjectByID<T>(ids.Key);
12✔
638

639
                    var set = new HashSet<T2>();
10✔
640

641
                    foreach (var val in ids.Value)
40✔
642
                        try
643
                        {
644
                            set.Add(GetObjectByID<T2>(val));
10✔
645
                        }
10✔
646
                        catch (KeyNotFoundException)
×
647
                        {
648
                            // skip missing objects (they will disapear next save anyway)
649
                            continue;
×
650
                        }
651

652
                    dictionary.Add(key, set);
10✔
653
                }
10✔
654
                catch (KeyNotFoundException)
2✔
655
                {
656
                    // skip missing container objects (they will disapear next save anyway)
657
                    continue;
2✔
658
                }
659

660
            return dictionary;
18✔
661
        }
662

663
        return new Dictionary<T, HashSet<T2>>();
222✔
664
    }
665

666
    private void Save<T, T2>(Dictionary<T, HashSet<T2>> collection, string filenameWithoutSuffix)
667
        where T : IMapsDirectlyToDatabaseTable
668
        where T2 : IMapsDirectlyToDatabaseTable
669
    {
670
        var file = Path.Combine(Directory.FullName, $"{filenameWithoutSuffix}.yaml");
22✔
671
        var serializer = new Serializer();
22✔
672

673
        // save the default and the ID
674
        File.WriteAllText(file, serializer.Serialize(
22✔
675
            collection.ToDictionary(
22✔
676
                k => k.Key.ID,
14✔
677
                v => v.Value.Select(c => c.ID).ToList()
24✔
678
            )));
22✔
679
    }
22✔
680
}
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