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

HicServices / RDMP / 6245535001

20 Sep 2023 07:44AM UTC coverage: 57.013%. First build
6245535001

push

github

web-flow
8.1.0 Release (#1628)

* Bump Newtonsoft.Json from 13.0.1 to 13.0.2

Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.1 to 13.0.2.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.1...13.0.2)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Bump NLog from 5.0.5 to 5.1.0

Bumps [NLog](https://github.com/NLog/NLog) from 5.0.5 to 5.1.0.
- [Release notes](https://github.com/NLog/NLog/releases)
- [Changelog](https://github.com/NLog/NLog/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/NLog/NLog/compare/v5.0.5...v5.1.0)

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

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

* Bump NLog from 5.0.5 to 5.1.0

* Fix -r flag - should have been --results-directory all along

* Bump Newtonsoft.Json from 13.0.1 to 13.0.2

* Bump YamlDotNet from 12.0.2 to 12.1.0

Bumps [YamlDotNet](https://github.com/aaubry/YamlDotNet) from 12.0.2 to 12.1.0.
- [Release notes](https://github.com/aaubry/YamlDotNet/releases)
- [Commits](https://github.com/aaubry/YamlDotNet/compare/v12.0.2...v12.1.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 Moq from 4.18.2 to 4.18.3

Bumps [Moq](https://github.com/moq/moq4) from 4.18.2 to 4.18.3.
- [Release notes](https://github.com/moq/moq4/releases)
- [Changelog](https://github.com/moq/moq4/blob/main/CHANGELOG.md)
- [Commits](https://github.com/moq/moq4/compare/v4.18.2...v4.18.3)

---
updated-dependencies:
- dependency-name: Moq
... (continued)

10732 of 20257 branches covered (0.0%)

Branch coverage included in aggregate %.

48141 of 48141 new or added lines in 1086 files covered. (100.0%)

30685 of 52388 relevant lines covered (58.57%)

7387.88 hits per line

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

86.84
/Rdmp.Core/MapsDirectlyToDatabaseTable/MemoryRepository.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.Concurrent;
9
using System.Collections.Generic;
10
using System.ComponentModel;
11
using System.Linq;
12
using System.Linq.Expressions;
13
using System.Reflection;
14
using Rdmp.Core.MapsDirectlyToDatabaseTable.Injection;
15
using Rdmp.Core.MapsDirectlyToDatabaseTable.Revertable;
16

17
namespace Rdmp.Core.MapsDirectlyToDatabaseTable;
18

19
/// <summary>
20
/// Implementation of <see cref="IRepository"/> which creates objects in memory instead of the database.
21
/// </summary>
22
public class MemoryRepository : IRepository
23
{
24
    protected int NextObjectId;
25

26
    public bool SupportsCommits => false;
22✔
27

28
    /// <summary>
29
    /// This is a concurrent hashset.  See https://stackoverflow.com/a/18923091
30
    /// </summary>
31
    protected readonly ConcurrentDictionary<IMapsDirectlyToDatabaseTable, byte> Objects =
7,422✔
32
        new();
7,422✔
33

34
    private readonly ConcurrentDictionary<IMapsDirectlyToDatabaseTable, HashSet<PropertyChangedExtendedEventArgs>>
7,422✔
35
        _propertyChanges = new();
7,422✔
36

37
    public event EventHandler<SaveEventArgs> Saving;
38
    public event EventHandler<IMapsDirectlyToDatabaseTableEventArgs> Inserting;
39
    public event EventHandler<IMapsDirectlyToDatabaseTableEventArgs> Deleting;
40

41
    public virtual void InsertAndHydrate<T>(T toCreate, Dictionary<string, object> constructorParameters)
42
        where T : IMapsDirectlyToDatabaseTable
43
    {
44
        NextObjectId++;
9,820✔
45
        toCreate.ID = NextObjectId;
9,820✔
46

47
        foreach (var kvp in constructorParameters)
70,332✔
48
        {
49
            var val = kvp.Value;
25,346✔
50

51
            //don't set nulls
52
            if (val == DBNull.Value)
25,346✔
53
                val = null;
2,374✔
54

55
            var prop = toCreate.GetType().GetProperty(kvp.Key);
25,346✔
56

57
            var strVal = kvp.Value as string;
25,346✔
58

59
            SetValue(toCreate, prop, strVal, val);
25,346✔
60
        }
61

62
        toCreate.Repository = this;
9,820✔
63

64
        Objects.TryAdd(toCreate, 0);
9,820✔
65

66
        toCreate.PropertyChanged += toCreate_PropertyChanged;
9,820✔
67

68
        NewObjectPool.Add(toCreate);
9,820✔
69

70
        Inserting?.Invoke(this, new IMapsDirectlyToDatabaseTableEventArgs(toCreate));
9,820!
71
    }
×
72

73
    protected virtual void SetValue<T>(T toCreate, PropertyInfo prop, string strVal, object val)
74
        where T : IMapsDirectlyToDatabaseTable
75
    {
76
        if (val == null)
25,346✔
77
        {
78
            prop.SetValue(toCreate, val);
2,458✔
79
            return;
2,458✔
80
        }
81

82
        var underlying = Nullable.GetUnderlyingType(prop.PropertyType);
22,888✔
83
        var type = underlying ?? prop.PropertyType;
22,888✔
84

85
        if (type.IsEnum)
22,888✔
86
        {
87
            if ( strVal != null)
1,578✔
88
                prop.SetValue(toCreate, Enum.Parse(type, strVal));
1,414✔
89
            else
90
            {
91
                prop.SetValue(toCreate, Enum.ToObject(type, val));
164✔
92
            }
93
        }
94
        else
95
            prop.SetValue(toCreate, Convert.ChangeType(val, type));
21,310✔
96
    }
21,310✔
97

98
    private void toCreate_PropertyChanged(object sender, PropertyChangedEventArgs e)
99
    {
100
        var changes = (PropertyChangedExtendedEventArgs)e;
4,645✔
101
        var onObject = (IMapsDirectlyToDatabaseTable)sender;
4,645✔
102

103
        //if we don't know about this object yet
104
        _propertyChanges.TryAdd(onObject, new HashSet<PropertyChangedExtendedEventArgs>());
4,645✔
105

106
        //if we already knew of a previous change
107
        var collision = _propertyChanges[onObject].SingleOrDefault(c => c.PropertyName.Equals(changes.PropertyName));
7,821✔
108

109
        //throw away that knowledge
110
        if (collision != null)
4,645✔
111
            _propertyChanges[onObject].Remove(collision);
56✔
112

113
        //we know about this change now
114
        _propertyChanges[onObject].Add(changes);
4,645✔
115
    }
4,645✔
116

117

118
    public T GetObjectByID<T>(int id) where T : IMapsDirectlyToDatabaseTable
119
    {
120
        if (id == 0)
4,117!
121
            return default;
×
122

123
        try
124
        {
125
            return Objects.Keys.OfType<T>().Single(o => o.ID == id);
53,251✔
126
        }
127
        catch (InvalidOperationException e)
8✔
128
        {
129
            throw new KeyNotFoundException($"Could not find {typeof(T).Name} with ID {id}", e);
8✔
130
        }
131
    }
4,109✔
132

133
    public T[] GetAllObjects<T>() where T : IMapsDirectlyToDatabaseTable
134
    {
135
        return Objects.Keys.OfType<T>().OrderBy(o => o.ID).ToArray();
75,268✔
136
    }
137

138
    public T[] GetAllObjectsWhere<T>(string property, object value1) where T : IMapsDirectlyToDatabaseTable
139
    {
140
        var prop = typeof(T).GetProperty(property);
1,810✔
141

142
        return GetAllObjects<T>().Where(o => Equals(prop.GetValue(o), value1)).ToArray();
4,026✔
143
    }
144

145
    public T[] GetAllObjectsWhere<T>(string property1, object value1, ExpressionType operand, string property2,
146
        object value2) where T : IMapsDirectlyToDatabaseTable
147
    {
148
        var prop1 = typeof(T).GetProperty(property1);
234✔
149
        var prop2 = typeof(T).GetProperty(property2);
234✔
150

151
        return operand switch
234!
152
        {
234✔
153
            ExpressionType.AndAlso => GetAllObjects<T>()
×
154
                .Where(o => Equals(prop1.GetValue(o), value1) && Equals(prop2.GetValue(o), value2))
×
155
                .ToArray(),
×
156
            ExpressionType.OrElse => GetAllObjects<T>()
234✔
157
                .Where(o => Equals(prop1.GetValue(o), value1) || Equals(prop2.GetValue(o), value2))
6✔
158
                .ToArray(),
234✔
159
            _ => throw new NotSupportedException("operand")
×
160
        };
234✔
161
    }
162

163
    public IEnumerable<IMapsDirectlyToDatabaseTable> GetAllObjects(Type t)
164
    {
165
        return Objects.Keys.Where(o => o.GetType() == t);
2,122✔
166
    }
167

168
    public T[] GetAllObjectsWithParent<T>(IMapsDirectlyToDatabaseTable parent) where T : IMapsDirectlyToDatabaseTable
169
    {
170
        //e.g. Catalogue_ID
171
        var propertyName = $"{parent.GetType().Name}_ID";
3,636✔
172

173
        var prop = typeof(T).GetProperty(propertyName);
3,636✔
174
        return Objects.Keys.OfType<T>().Where(o => prop.GetValue(o) as int? == parent.ID).Cast<T>().OrderBy(o => o.ID)
36,630✔
175
            .ToArray();
3,636✔
176
    }
177

178
    public T[] GetAllObjectsWithParent<T, T2>(T2 parent) where T : IMapsDirectlyToDatabaseTable, IInjectKnown<T2>
179
        where T2 : IMapsDirectlyToDatabaseTable
180
    {
181
        //e.g. Catalogue_ID
182
        var propertyName = $"{typeof(T2).Name}_ID";
1,208✔
183

184
        var prop = typeof(T).GetProperty(propertyName);
1,208✔
185
        return Objects.Keys.OfType<T>().Where(o => prop.GetValue(o) as int? == parent.ID).Cast<T>().OrderBy(o => o.ID)
26,918✔
186
            .ToArray();
1,208✔
187
    }
188

189
    public virtual void SaveToDatabase(IMapsDirectlyToDatabaseTable oTableWrapperObject)
190
    {
191
        Saving?.Invoke(this, new SaveEventArgs(oTableWrapperObject));
4,550!
192

193
        var existing = Objects.Keys.FirstOrDefault(k => k.Equals(oTableWrapperObject));
236,191✔
194

195
        // If saving a new reference to an existing object then we should update our tracked
196
        // objects to the latest reference since the old one is stale
197
        if (!ReferenceEquals(existing, oTableWrapperObject))
4,550✔
198
        {
199
            Objects.TryRemove(oTableWrapperObject, out _);
6✔
200
            Objects.TryAdd(oTableWrapperObject, 0);
6✔
201
        }
202

203
        //forget about property changes (since it's 'saved' now)
204
        _propertyChanges.TryRemove(oTableWrapperObject, out _);
4,550✔
205
    }
4,550✔
206

207
    public virtual void DeleteFromDatabase(IMapsDirectlyToDatabaseTable oTableWrapperObject)
208
    {
209
        CascadeDeletes(oTableWrapperObject);
514✔
210

211
        Objects.TryRemove(oTableWrapperObject, out _);
514✔
212

213
        //forget about property changes (since it's been deleted)
214
        _propertyChanges.TryRemove(oTableWrapperObject, out _);
514✔
215

216
        Deleting?.Invoke(this, new IMapsDirectlyToDatabaseTableEventArgs(oTableWrapperObject));
514!
217
    }
×
218

219
    /// <summary>
220
    /// Override to replicate any database delete cascade relationships (e.g. deleting all
221
    /// CatalogueItem when a Catalogue is deleted)
222
    /// </summary>
223
    /// <param name="oTableWrapperObject"></param>
224
    protected virtual void CascadeDeletes(IMapsDirectlyToDatabaseTable oTableWrapperObject)
225
    {
226
    }
514✔
227

228
    public void RevertToDatabaseState(IMapsDirectlyToDatabaseTable mapsDirectlyToDatabaseTable)
229
    {
230
        //Mark any cached data as out of date
231
        if (mapsDirectlyToDatabaseTable is IInjectKnown inject)
68✔
232
            inject.ClearAllInjections();
38✔
233

234
        if (!_propertyChanges.ContainsKey(mapsDirectlyToDatabaseTable))
68✔
235
            return;
58✔
236

237
        var type = mapsDirectlyToDatabaseTable.GetType();
10✔
238

239
        foreach (var e in
30✔
240
                 _propertyChanges[mapsDirectlyToDatabaseTable]
10✔
241
                     .ToArray()) //call ToArray to avoid cyclical events on SetValue
10✔
242
        {
243
            var prop = type.GetProperty(e.PropertyName);
10✔
244
            prop.SetValue(mapsDirectlyToDatabaseTable, e.OldValue); //reset the old values
10✔
245
        }
246

247
        //forget about all changes now
248
        _propertyChanges.TryRemove(mapsDirectlyToDatabaseTable, out _);
10✔
249
    }
10✔
250

251
    public RevertableObjectReport HasLocalChanges(IMapsDirectlyToDatabaseTable mapsDirectlyToDatabaseTable)
252
    {
253
        //if we don't know about it then it was deleted
254
        if (!Objects.ContainsKey(mapsDirectlyToDatabaseTable))
248!
255
            return new RevertableObjectReport { Evaluation = ChangeDescription.DatabaseCopyWasDeleted };
×
256

257
        //if it has no changes (since a save)
258
        if (!_propertyChanges.ContainsKey(mapsDirectlyToDatabaseTable))
248✔
259
            return new RevertableObjectReport { Evaluation = ChangeDescription.NoChanges };
238✔
260

261
        //we have local 'unsaved' changes
262
        var type = mapsDirectlyToDatabaseTable.GetType();
10✔
263
        var differences = _propertyChanges[mapsDirectlyToDatabaseTable].Select(
10✔
264
                d => new RevertablePropertyDifference(type.GetProperty(d.PropertyName), d.NewValue, d.OldValue))
10✔
265
            .ToList();
10✔
266

267
        return new RevertableObjectReport(differences) { Evaluation = ChangeDescription.DatabaseCopyDifferent };
10✔
268
    }
269

270
    /// <inheritdoc/>
271
    public bool AreEqual(IMapsDirectlyToDatabaseTable obj1, object obj2)
272
    {
273
        if (obj1 == null && obj2 != null)
302,522!
274
            return false;
×
275

276
        if (obj2 == null && obj1 != null)
302,522✔
277
            return false;
2✔
278

279
        if (obj1 == null && obj2 == null)
302,520!
280
            throw new NotSupportedException(
×
281
                "Why are you comparing two null things against one another with this method?");
×
282

283
        return obj1.GetType() == obj2.GetType() && obj1.ID == ((IMapsDirectlyToDatabaseTable)obj2).ID;
302,520✔
284
    }
285

286
    /// <inheritdoc/>
287
    public int GetHashCode(IMapsDirectlyToDatabaseTable obj1) => obj1.GetType().GetHashCode() * obj1.ID;
212,255✔
288

289
    public Version GetVersion() => GetType().Assembly.GetName().Version;
×
290

291

292
    public bool StillExists<T>(int allegedParent) where T : IMapsDirectlyToDatabaseTable
293
    {
294
        return Objects.Keys.OfType<T>().Any(o => o.ID == allegedParent);
×
295
    }
296

297
    public bool StillExists(IMapsDirectlyToDatabaseTable o) => Objects.ContainsKey(o);
310✔
298

299
    public bool StillExists(Type objectType, int objectId)
300
    {
301
        return Objects.Keys.Any(o => o.GetType() == objectType && o.ID == objectId);
2,693✔
302
    }
303

304
    public IMapsDirectlyToDatabaseTable GetObjectByID(Type objectType, int objectId)
305
    {
306
        return Objects.Keys.SingleOrDefault(o => o.GetType() == objectType && objectId == o.ID)
1,790✔
307
               ?? throw new KeyNotFoundException(
94✔
308
                   $"Could not find object of Type '{objectType}' with ID '{objectId}' in {nameof(MemoryRepository)}");
94✔
309
    }
310

311
    public IEnumerable<T> GetAllObjectsInIDList<T>(IEnumerable<int> ids) where T : IMapsDirectlyToDatabaseTable
312
    {
313
        var hs = new HashSet<int>(ids);
18✔
314
        return Objects.Keys.OfType<T>().Where(o => hs.Contains(o.ID)).OrderBy(o => o.ID);
532✔
315
    }
316

317
    public IEnumerable<IMapsDirectlyToDatabaseTable> GetAllObjectsInIDList(Type elementType, IEnumerable<int> ids)
318
    {
319
        var hs = new HashSet<int>(ids);
16✔
320
        return GetAllObjects(elementType).Where(o => hs.Contains(o.ID));
102✔
321
    }
322

323
    public void SaveSpecificPropertyOnlyToDatabase(IMapsDirectlyToDatabaseTable entity, string propertyName,
324
        object propertyValue)
325
    {
326
        var prop = entity.GetType().GetProperty(propertyName);
1,032✔
327
        prop.SetValue(entity, propertyValue);
1,032✔
328
        SaveToDatabase(entity);
1,032✔
329
    }
1,032✔
330

331

332
    public IMapsDirectlyToDatabaseTable[] GetAllObjectsInDatabase()
333
    {
334
        return Objects.Keys.OrderBy(o => o.ID).ToArray();
106✔
335
    }
336

337
    public bool SupportsObjectType(Type type) => typeof(IMapsDirectlyToDatabaseTable).IsAssignableFrom(type);
344✔
338

339
    public void TestConnection()
340
    {
341
    }
×
342

343
    public virtual void Clear()
344
    {
345
        Objects.Clear();
1,974✔
346
    }
1,974✔
347

348
    public Type[] GetCompatibleTypes()
349
    {
350
        return
106✔
351
            GetType().Assembly.GetTypes()
106✔
352
                .Where(
106✔
353
                    t =>
106✔
354
                        typeof(IMapsDirectlyToDatabaseTable).IsAssignableFrom(t)
241,044!
355
                        && !t.IsAbstract
241,044✔
356
                        && !t.IsInterface
241,044✔
357

241,044✔
358
                        //nothing called spontaneous
241,044✔
359
                        && !t.Name.Contains("Spontaneous")
241,044✔
360

241,044✔
361
                        //or with a spontaneous base class
241,044✔
362
                        && (t.BaseType == null || !t.BaseType.Name.Contains("Spontaneous"))
241,044✔
363
                ).ToArray();
106✔
364
    }
365

366

367
    public IDisposable BeginNewTransaction() => new EmptyDisposeable();
×
368

369
    public void EndTransaction(bool commit)
370
    {
371
    }
×
372
}
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

© 2025 Coveralls, Inc