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

HicServices / RDMP / 10214502522

02 Aug 2024 11:01AM UTC coverage: 57.295% (-0.004%) from 57.299%
10214502522

push

github

JFriel
Merge branch 'develop' of https://github.com/HicServices/RDMP

11089 of 20820 branches covered (53.26%)

Branch coverage included in aggregate %.

66 of 116 new or added lines in 8 files covered. (56.9%)

1 existing line in 1 file now uncovered.

31368 of 53282 relevant lines covered (58.87%)

7983.81 hits per line

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

78.73
/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,742✔
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
        if (File.Exists(GetEncryptionKeyPathFile()))
48!
52
        {
NEW
53
            EncryptionKeyPath = File.ReadLines(GetEncryptionKeyPathFile()).First();
×
54
            // Check if the file does exist but we were confused by stray whitespace:
NEW
55
            if (!File.Exists(EncryptionKeyPath) && char.IsWhiteSpace(EncryptionKeyPath[^1]))
×
56
            {
NEW
57
                var trimmed = EncryptionKeyPath.TrimEnd();
×
NEW
58
                if (File.Exists(trimmed)) EncryptionKeyPath = trimmed;
×
59
            }
60
        }
61
        
62
        LoadObjects();      
48✔
63

64
        // Don't create new objects with the ID of existing objects
65
        NextObjectId = Objects.IsEmpty ? 0 : Objects.Max(o => o.Key.ID);
202✔
66
    }
48✔
67

68
    /// <summary>
69
    /// 
70
    /// </summary>
71
    /// <returns></returns>
72
    public static ISerializer CreateSerializer(IEnumerable<Type> supportedTypes)
73
    {
74
        var builder = new SerializerBuilder();
56✔
75
        builder.WithTypeConverter(new VersionYamlTypeConverter());
56✔
76

77
        foreach (var type in supportedTypes)
9,040✔
78
        {
79
            var respect = TableRepository.GetPropertyInfos(type);
4,464✔
80

81
            builder = type.GetProperties().Where(prop => !respect.Contains(prop)).Aggregate(builder,
58,480✔
82
                (current, prop) => current.WithAttributeOverride(type, prop.Name, new YamlIgnoreAttribute()));
26,616✔
83
        }
84

85
        return builder.Build();
56✔
86
    }
87

88
    private void LoadObjects()
89
    {
90
        var subdirs = Directory.GetDirectories();
48✔
91
        var builder = new DeserializerBuilder();
48✔
92
        builder.WithTypeConverter(new VersionYamlTypeConverter());
48✔
93

94
        var deserializer = builder.Build();
48✔
95

96
        foreach (var t in GetCompatibleTypes().OrderBy(ObjectDependencyOrder))
7,776✔
97
        {
98
            // find the directory that contains all the YAML files e.g. MyDir/Catalogue/
99
            var typeDir = subdirs.FirstOrDefault(d => d.Name.Equals(t.Name));
82,104✔
100

101
            if (typeDir == null)
3,840✔
102
            {
103
                Directory.CreateSubdirectory(t.Name);
1,920✔
104
                continue;
1,920✔
105
            }
106

107
            lock (lockFs)
1,920✔
108
            {
109
                foreach (var yaml in typeDir.EnumerateFiles("*.yaml"))
4,148✔
110
                    try
111
                    {
112
                        var obj = (IMapsDirectlyToDatabaseTable)deserializer.Deserialize(
154✔
113
                            File.ReadAllText(yaml.FullName), t);
154✔
114
                        SetRepositoryOnObject(obj);
154✔
115
                        Objects.TryAdd(obj, 0);
154✔
116
                    }
154✔
117
                    catch (Exception ex)
×
118
                    {
119
                        throw new Exception($"Error loading object in file {yaml.FullName}", ex);
×
120
                    }
121
            }
122
        }
123

124
        LoadDefaults();
48✔
125

126
        LoadDataExportProperties();
48✔
127

128
        LoadCredentialsDictionary();
48✔
129

130
        PackageDictionary = Load<IExtractableDataSetPackage, IExtractableDataSet>(nameof(PackageDictionary)) ??
48!
131
                            PackageDictionary;
48✔
132

133
        GovernanceCoverage = Load<GovernancePeriod, ICatalogue>(nameof(GovernanceCoverage)) ?? GovernanceCoverage;
48!
134

135
        ForcedJoins = Load<AggregateConfiguration, ITableInfo>(nameof(ForcedJoins)) ?? ForcedJoins;
48!
136

137
        LoadCohortContainerContents();
48✔
138

139
        LoadWhereSubContainers();
48✔
140
    }
48✔
141

142
    private int ObjectDependencyOrder(Type arg)
143
    {
144
        // Load Plugin objects before dependent children
145
        if (arg == typeof(Plugin))
3,840!
146
            return 1;
×
147

148
        return arg == typeof(LoadModuleAssembly) ? 2 : 3;
3,840!
149
    }
150

151

152
    /// <summary>
153
    /// Sets <see cref="IMapsDirectlyToDatabaseTable.Repository"/> on <paramref name="obj"/>.
154
    /// Override to also set other destination repo specific fields
155
    /// </summary>
156
    /// <param name="obj"></param>
157
    protected virtual void SetRepositoryOnObject(IMapsDirectlyToDatabaseTable obj)
158
    {
159
        obj.Repository = this;
338!
160

161
        switch (obj)
162
        {
163
            case DataAccessCredentials creds:
164
                creds.SetRepository(this);
4✔
165
                break;
4✔
166
            case ExternalDatabaseServer eds:
167
                eds.SetRepository(this);
4✔
168
                break;
4✔
169
            case ExternalCohortTable ect:
170
                ect.SetRepository(this);
×
171
                break;
×
172
            case RemoteRDMP remote:
173
                remote.SetRepository(this);
×
174
                break;
×
175
            case ConcreteContainer container:
176
                container.SetManager(this);
16✔
177
                break;
178
        }
179
    }
16✔
180

181
    public override void InsertAndHydrate<T>(T toCreate, Dictionary<string, object> constructorParameters)
182
    {
183
        base.InsertAndHydrate(toCreate, constructorParameters);
134✔
184

185
        // put it on disk
186
        lock (lockFs)
134✔
187
        {
188
            SaveToDatabase(toCreate);
134✔
189
        }
134✔
190
    }
134✔
191

192
    public override void DeleteFromDatabase(IMapsDirectlyToDatabaseTable oTableWrapperObject)
193
    {
194
        lock (lockFs)
6✔
195
        {
196
            base.DeleteFromDatabase(oTableWrapperObject);
6✔
197
            File.Delete(GetPath(oTableWrapperObject));
6✔
198
        }
6✔
199
    }
6✔
200

201
    public override void SaveToDatabase(IMapsDirectlyToDatabaseTable o)
202
    {
203
        base.SaveToDatabase(o);
184✔
204

205
        SetRepositoryOnObject(o);
184✔
206

207
        var yaml = _serializer.Serialize(o);
184✔
208

209
        lock (lockFs)
184✔
210
        {
211
            File.WriteAllText(GetPath(o), yaml);
184✔
212
        }
184✔
213
    }
184✔
214

215
    /// <summary>
216
    /// Returns the path on disk in which the yaml file for <paramref name="o"/> is stored
217
    /// </summary>
218
    /// <param name="o"></param>
219
    /// <returns></returns>
220
    private string GetPath(IMapsDirectlyToDatabaseTable o) =>
221
        Path.Combine(Directory.FullName, o.GetType().Name, $"{o.ID}.yaml");
190✔
222

223
    public override void DeleteEncryptionKeyPath()
224
    {
225
        base.DeleteEncryptionKeyPath();
×
226

227
        if (File.Exists(GetEncryptionKeyPathFile()))
×
228
            File.Delete(GetEncryptionKeyPathFile());
×
229
    }
×
230

231
    public override void SetEncryptionKeyPath(string fullName)
232
    {
233
        base.SetEncryptionKeyPath(fullName);
×
234

235
        // if setting it to null
236
        if (string.IsNullOrWhiteSpace(fullName))
×
237
        {
238
            // delete the file on disk
239
            if (File.Exists(GetEncryptionKeyPathFile()))
×
240
                File.Delete(GetEncryptionKeyPathFile());
×
241
        }
242
        else
243
        {
244
            File.WriteAllText(GetEncryptionKeyPathFile(), fullName);
×
245
        }
246
    }
×
247

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

250
    #region Server Defaults Persistence
251

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

254
    public override void SetDefault(PermissableDefaults toChange, IExternalDatabaseServer externalDatabaseServer)
255
    {
256
        base.SetDefault(toChange, externalDatabaseServer);
2✔
257

258
        SaveDefaults();
2✔
259
    }
2✔
260

261
    private void SaveDefaults()
262
    {
263
        var serializer = new Serializer();
2✔
264

265
        // save the default and the ID
266
        File.WriteAllText(GetDefaultsFile(),
2✔
267
            serializer.Serialize(Defaults.ToDictionary(k => k.Key, v => v.Value?.ID ?? 0)));
34✔
268
    }
2✔
269

270
    public void LoadDefaults()
271
    {
272
        var deserializer = new Deserializer();
48✔
273

274
        var defaultsFile = GetDefaultsFile();
48✔
275

276
        if (File.Exists(defaultsFile))
48✔
277
        {
278
            var yaml = File.ReadAllText(defaultsFile);
2✔
279
            var objectIds = deserializer.Deserialize<Dictionary<PermissableDefaults, int>>(yaml);
2✔
280

281
            // file exists but is empty
282
            if (objectIds == null)
2!
283
                return;
×
284

285
            Defaults = objectIds.ToDictionary(
2✔
286
                k => k.Key,
16✔
287
                v => v.Value == 0
18✔
288
                    ? null
18✔
289
                    : (IExternalDatabaseServer)GetObjectByIDIfExists<ExternalDatabaseServer>(v.Value));
18✔
290
        }
291
    }
48✔
292

293
    /// <summary>
294
    /// Returns the object referenced or null if it has been deleted on the sly (e.g. by user deleting .yaml files on disk)
295
    /// </summary>
296
    /// <typeparam name="T"></typeparam>
297
    /// <param name="id"></param>
298
    /// <returns></returns>
299
    private T GetObjectByIDIfExists<T>(int id) where T : DatabaseEntity
300
    {
301
        try
302
        {
303
            return GetObjectByID<T>(id);
6✔
304
        }
305
        catch (KeyNotFoundException)
×
306
        {
307
            return null;
×
308
        }
309
    }
6✔
310

311
    #endregion
312

313
    #region DataExportProperties Persistence
314

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

317
    public void LoadDataExportProperties()
318
    {
319
        var deserializer = new Deserializer();
48✔
320

321
        var defaultsFile = GetDataExportPropertiesFile();
48✔
322

323
        if (File.Exists(defaultsFile))
48✔
324
        {
325
            var yaml = File.ReadAllText(defaultsFile);
2✔
326
            var props = deserializer.Deserialize<Dictionary<DataExportProperty, string>>(yaml);
2✔
327

328
            if (props != null)
2✔
329
                PropertiesDictionary = props;
2✔
330
        }
331
    }
48✔
332

333
    private void SaveDataExportProperties()
334
    {
335
        var serializer = new Serializer();
2✔
336

337
        // save the default and the ID
338
        File.WriteAllText(GetDataExportPropertiesFile(), serializer.Serialize(PropertiesDictionary));
2✔
339
    }
2✔
340

341
    public override void SetValue(DataExportProperty property, string value)
342
    {
343
        base.SetValue(property, value);
2✔
344
        SaveDataExportProperties();
2✔
345
    }
2✔
346

347
    #endregion
348

349
    public override void AddDataSetToPackage(IExtractableDataSetPackage package, IExtractableDataSet dataSet)
350
    {
351
        base.AddDataSetToPackage(package, dataSet);
2✔
352
        Save(PackageDictionary, nameof(PackageDictionary));
2✔
353
    }
2✔
354

355
    public override void RemoveDataSetFromPackage(IExtractableDataSetPackage package, IExtractableDataSet dataSet)
356
    {
357
        base.RemoveDataSetFromPackage(package, dataSet);
×
358
        Save(PackageDictionary, nameof(PackageDictionary));
×
359
    }
×
360

361

362
    public override void Link(GovernancePeriod governancePeriod, ICatalogue catalogue)
363
    {
364
        base.Link(governancePeriod, catalogue);
2✔
365
        Save(GovernanceCoverage, nameof(GovernanceCoverage));
2✔
366
    }
2✔
367

368
    public override void Unlink(GovernancePeriod governancePeriod, ICatalogue catalogue)
369
    {
370
        base.Unlink(governancePeriod, catalogue);
×
371
        Save(GovernanceCoverage, nameof(GovernanceCoverage));
×
372
    }
×
373

374
    public override void CreateLinkBetween(AggregateConfiguration configuration, ITableInfo tableInfo)
375
    {
376
        base.CreateLinkBetween(configuration, tableInfo);
2✔
377
        Save(ForcedJoins, nameof(ForcedJoins));
2✔
378
    }
2✔
379

380
    public override void BreakLinkBetween(AggregateConfiguration configuration, ITableInfo tableInfo)
381
    {
382
        base.BreakLinkBetween(configuration, tableInfo);
×
383
        Save(ForcedJoins, nameof(ForcedJoins));
×
384
    }
×
385

386
    #region Persist CredentialsDictionary
387

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

390
    public void LoadCredentialsDictionary()
391
    {
392
        var deserializer = new Deserializer();
48✔
393

394
        var file = GetCredentialsDictionaryFile();
48✔
395

396
        if (File.Exists(file))
48✔
397
        {
398
            var yaml = File.ReadAllText(file);
2✔
399

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

402
            // file exists but is empty
403
            if (ids == null)
2!
404
                return;
×
405

406
            CredentialsDictionary = new Dictionary<ITableInfo, Dictionary<DataAccessContext, DataAccessCredentials>>();
2✔
407

408
            foreach (var tableToCredentialUsage in ids)
8✔
409
            {
410
                var table = GetObjectByIDIfExists<TableInfo>(tableToCredentialUsage.Key);
2✔
411

412
                // TableInfo was deleted on the sly
413
                if (table == null)
2✔
414
                    continue;
415

416
                var valDictionary = new Dictionary<DataAccessContext, DataAccessCredentials>();
2✔
417
                foreach (var (usage, value) in tableToCredentialUsage.Value)
8✔
418
                {
419
                    var credential = GetObjectByIDIfExists<DataAccessCredentials>(value);
2✔
420

421
                    // Credentials can be deleted on the sly
422
                    if (credential != null) valDictionary.Add(usage, credential);
4✔
423
                }
424

425
                CredentialsDictionary.Add(table, valDictionary);
2✔
426
            }
427
        }
428
    }
48✔
429

430
    private void SaveCredentialsDictionary()
431
    {
432
        var serializer = new Serializer();
2✔
433

434
        var ids =
2✔
435
            CredentialsDictionary.ToDictionary(
2✔
436
                k => k.Key.ID,
2✔
437
                v => v.Value.ToDictionary(k => k.Key, v => v.Value.ID));
8✔
438

439
        // save the default and the ID
440
        File.WriteAllText(GetCredentialsDictionaryFile(), serializer.Serialize(ids));
2✔
441
    }
2✔
442

443
    public override void BreakAllLinksBetween(DataAccessCredentials credentials, ITableInfo tableInfo)
444
    {
445
        base.BreakAllLinksBetween(credentials, tableInfo);
×
446

447
        SaveCredentialsDictionary();
×
448
    }
×
449

450
    public override void BreakLinkBetween(DataAccessCredentials credentials, ITableInfo tableInfo,
451
        DataAccessContext context)
452
    {
453
        base.BreakLinkBetween(credentials, tableInfo, context);
×
454
        SaveCredentialsDictionary();
×
455
    }
×
456

457
    public override void CreateLinkBetween(DataAccessCredentials credentials, ITableInfo tableInfo,
458
        DataAccessContext context)
459
    {
460
        base.CreateLinkBetween(credentials, tableInfo, context);
2✔
461
        SaveCredentialsDictionary();
2✔
462
    }
2✔
463

464
    #endregion
465

466

467
    #region Persist Cohort Containers
468

469
    private void SaveCohortContainerContents(CohortAggregateContainer toSave)
470
    {
471
        var dir = Path.Combine(Directory.FullName, nameof(PersistCohortContainerContent));
4✔
472
        var file = Path.Combine(dir, $"{toSave.ID}.yaml");
4✔
473

474
        var serializer = new Serializer();
4✔
475
        var yaml = serializer.Serialize(CohortContainerContents[toSave]
4✔
476
            .Select(c => new PersistCohortContainerContent(c)).ToList());
10✔
477
        File.WriteAllText(file, yaml);
4✔
478
    }
4✔
479

480
    private void LoadCohortContainerContents()
481
    {
482
        var dir = new DirectoryInfo(Path.Combine(Directory.FullName, nameof(PersistCohortContainerContent)));
48✔
483

484
        if (!dir.Exists)
48✔
485
            dir.Create();
24✔
486

487
        var deserializer = new Deserializer();
48✔
488

489
        foreach (var f in dir.GetFiles("*.yaml").ToArray())
100✔
490
        {
491
            var id = int.Parse(Path.GetFileNameWithoutExtension(f.Name));
2✔
492

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

495
            // file exists but is empty
496
            if (content == null)
2✔
497
                continue;
498

499
            try
500
            {
501
                CohortAggregateContainer container;
502

503
                try
504
                {
505
                    container = GetObjectByID<CohortAggregateContainer>(id);
2✔
506
                }
2✔
507
                catch (KeyNotFoundException)
×
508
                {
509
                    // The container doesn't exist anymore
510
                    f.Delete();
×
511
                    continue;
×
512
                }
513

514
                CohortContainerContents.Add(container,
2✔
515
                    new HashSet<CohortContainerContent>(content.Select(c => c.GetContent(this))));
6✔
516
            }
2✔
517
            catch (Exception ex)
×
518
            {
519
                throw new Exception($"Error reading file {f.FullName}", ex);
×
520
            }
521
        }
522
    }
48✔
523

524
    public override void Add(CohortAggregateContainer parent, AggregateConfiguration child, int order)
525
    {
526
        base.Add(parent, child, order);
2✔
527
        SaveCohortContainerContents(parent);
2✔
528
    }
2✔
529

530
    public override void Add(CohortAggregateContainer parent, CohortAggregateContainer child)
531
    {
532
        base.Add(parent, child);
2✔
533
        SaveCohortContainerContents(parent);
2✔
534
    }
2✔
535

536
    public override void Remove(CohortAggregateContainer parent, AggregateConfiguration child)
537
    {
538
        base.Remove(parent, child);
×
539
        SaveCohortContainerContents(parent);
×
540
    }
×
541

542
    public override void SetOrder(AggregateConfiguration child, int newOrder)
543
    {
544
        base.SetOrder(child, newOrder);
×
545
        SaveCohortContainerContents(child.GetCohortAggregateContainerIfAny());
×
546
    }
×
547

548
    public override void Remove(CohortAggregateContainer parent, CohortAggregateContainer child)
549
    {
550
        base.Remove(parent, child);
×
551
        SaveCohortContainerContents(parent);
×
552
    }
×
553

554
    private class PersistCohortContainerContent
555
    {
556
        public string Type { get; set; }
28✔
557
        public int ID { get; set; }
26✔
558
        public int Order { get; set; }
26✔
559

560
        public PersistCohortContainerContent()
4✔
561
        {
562
        }
4✔
563

564
        public PersistCohortContainerContent(CohortContainerContent c)
6✔
565
        {
566
            Type = c.Orderable.GetType().Name;
6✔
567
            ID = ((IMapsDirectlyToDatabaseTable)c.Orderable).ID;
6✔
568
            Order = c.Order;
6✔
569
        }
6✔
570

571

572
        public CohortContainerContent GetContent(YamlRepository repository)
573
        {
574
            if (Type.Equals(nameof(AggregateConfiguration)))
4✔
575
                return new CohortContainerContent(repository.GetObjectByID<AggregateConfiguration>(ID), Order);
2✔
576

577
            return Type.Equals(nameof(CohortAggregateContainer))
2!
578
                ? new CohortContainerContent(repository.GetObjectByID<CohortAggregateContainer>(ID), Order)
2✔
579
                : throw new Exception($"Unexpected IOrderable Type name '{Type}'");
2✔
580
        }
581
    }
582

583
    #endregion
584

585
    public override void MakeIntoAnOrphan(IContainer container)
586
    {
587
        base.MakeIntoAnOrphan(container);
4✔
588
        SaveWhereSubContainers();
4✔
589
    }
4✔
590

591
    public override void AddSubContainer(IContainer parent, IContainer child)
592
    {
593
        base.AddSubContainer(parent, child);
4✔
594
        SaveWhereSubContainers();
4✔
595
    }
4✔
596

597
    private void SaveWhereSubContainers()
598
    {
599
        Save(WhereSubContainers.Where(kvp => kvp.Key is FilterContainer)
16✔
600
            .ToDictionary(
8✔
601
                k => k.Key,
×
602
                v => v.Value), "ExtractionFilters");
8✔
603

604
        Save(WhereSubContainers.Where(kvp => kvp.Key is AggregateFilterContainer)
16✔
605
            .ToDictionary(
8✔
606
                k => k.Key,
8✔
607
                v => v.Value), "AggregateFilters");
16✔
608
    }
8✔
609

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

612
    private void LoadWhereSubContainers()
613
    {
614
        foreach (var c in Load<FilterContainer, FilterContainer>("ExtractionFilters") ??
96!
615
                          new Dictionary<FilterContainer, HashSet<FilterContainer>>())
48✔
616
            WhereSubContainers.Add(c.Key, new HashSet<IContainer>(c.Value));
×
617
        foreach (var c in Load<AggregateFilterContainer, AggregateFilterContainer>("AggregateFilters") ??
104!
618
                          new Dictionary<AggregateFilterContainer, HashSet<AggregateFilterContainer>>())
48✔
619
            WhereSubContainers.Add(c.Key, new HashSet<IContainer>(c.Value));
4✔
620
    }
48✔
621

622
    private Dictionary<T, HashSet<T2>> Load<T, T2>(string filenameWithoutSuffix)
623
        where T : IMapsDirectlyToDatabaseTable
624
        where T2 : IMapsDirectlyToDatabaseTable
625
    {
626
        var deserializer = new Deserializer();
240✔
627

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

630
        if (File.Exists(file))
240✔
631
        {
632
            var yaml = File.ReadAllText(file);
18✔
633

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

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

638
            //file exists but is empty
639
            if (dict == null)
18!
640
                return null;
×
641

642
            foreach (var ids in dict)
60✔
643
                try
644
                {
645
                    var key = GetObjectByID<T>(ids.Key);
12✔
646

647
                    var set = new HashSet<T2>();
10✔
648

649
                    foreach (var val in ids.Value)
40✔
650
                        try
651
                        {
652
                            set.Add(GetObjectByID<T2>(val));
10✔
653
                        }
10✔
654
                        catch (KeyNotFoundException)
×
655
                        {
656
                            // skip missing objects (they will disapear next save anyway)
657
                            continue;
×
658
                        }
659

660
                    dictionary.Add(key, set);
10✔
661
                }
10✔
662
                catch (KeyNotFoundException)
2✔
663
                {
664
                    // skip missing container objects (they will disapear next save anyway)
665
                    continue;
2✔
666
                }
667

668
            return dictionary;
18✔
669
        }
670

671
        return new Dictionary<T, HashSet<T2>>();
222✔
672
    }
673

674
    private void Save<T, T2>(Dictionary<T, HashSet<T2>> collection, string filenameWithoutSuffix)
675
        where T : IMapsDirectlyToDatabaseTable
676
        where T2 : IMapsDirectlyToDatabaseTable
677
    {
678
        var file = Path.Combine(Directory.FullName, $"{filenameWithoutSuffix}.yaml");
22✔
679
        var serializer = new Serializer();
22✔
680

681
        // save the default and the ID
682
        File.WriteAllText(file, serializer.Serialize(
22✔
683
            collection.ToDictionary(
22✔
684
                k => k.Key.ID,
14✔
685
                v => v.Value.Select(c => c.ID).ToList()
24✔
686
            )));
22✔
687
    }
22✔
688
}
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