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

HicServices / RDMP / 7102803153

05 Dec 2023 03:19PM UTC coverage: 56.338% (-0.4%) from 56.745%
7102803153

push

github

web-flow
Bump NUnit from 3.14.0 to 4.0.0 (#1686)

10562 of 20305 branches covered (0.0%)

Branch coverage included in aggregate %.

64 of 96 new or added lines in 21 files covered. (66.67%)

161 existing lines in 17 files now uncovered.

30382 of 52370 relevant lines covered (58.01%)

7337.5 hits per line

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

84.07
/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
using Rdmp.Core.ReusableLibraryCode.Annotations;
17

18
namespace Rdmp.Core.MapsDirectlyToDatabaseTable;
19

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

27
    public bool SupportsCommits => false;
12✔
28

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

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

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

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

48
        foreach (var kvp in constructorParameters)
62,324✔
49
        {
50
            var val = kvp.Value;
22,326✔
51

52
            //don't set nulls
53
            if (val == DBNull.Value)
22,326✔
54
                val = null;
2,124✔
55

56
            var prop = toCreate.GetType().GetProperty(kvp.Key);
22,326✔
57

58
            var strVal = kvp.Value as string;
22,326✔
59

60
            SetValue(toCreate, prop, strVal, val);
22,326✔
61
        }
62

63
        toCreate.Repository = this;
8,836✔
64

65
        Objects.TryAdd(toCreate, 0);
8,836✔
66

67
        toCreate.PropertyChanged += toCreate_PropertyChanged;
8,836✔
68

69
        NewObjectPool.Add(toCreate);
8,836✔
70

71
        Inserting?.Invoke(this, new IMapsDirectlyToDatabaseTableEventArgs(toCreate));
8,836!
72
    }
×
73

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

83
        var underlying = Nullable.GetUnderlyingType(prop.PropertyType);
20,128✔
84
        var type = underlying ?? prop.PropertyType;
20,128✔
85

86
        if (type.IsEnum)
20,128✔
87
        {
88
            if ( strVal != null)
1,426✔
89
                prop.SetValue(toCreate, Enum.Parse(type, strVal));
1,278✔
90
            else
91
            {
92
                prop.SetValue(toCreate, Enum.ToObject(type, val));
148✔
93
            }
94
        }
95
        else
96
            prop.SetValue(toCreate, Convert.ChangeType(val, type));
18,702✔
97
    }
18,702✔
98

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

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

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

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

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

118

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

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

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

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

143
        return GetAllObjects<T>().Where(o => Equals(prop.GetValue(o), value1)).ToArray();
3,912✔
144
    }
145

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

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

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

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

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

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

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

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

194
        var existing = Objects.Keys.FirstOrDefault(k => k.Equals(oTableWrapperObject));
198,990✔
195

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

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

208
    public virtual void DeleteFromDatabase(IMapsDirectlyToDatabaseTable oTableWrapperObject)
209
    {
210
        CascadeDeletes(oTableWrapperObject);
500✔
211

212
        Objects.TryRemove(oTableWrapperObject, out _);
500✔
213

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

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

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

229
    public void RevertToDatabaseState([NotNull] IMapsDirectlyToDatabaseTable mapsDirectlyToDatabaseTable)
230
    {
231
        //Mark any cached data as out of date
232
        if (mapsDirectlyToDatabaseTable is IInjectKnown inject)
52✔
233
            inject.ClearAllInjections();
22✔
234

235
        if (!_propertyChanges.TryGetValue(mapsDirectlyToDatabaseTable, out var changedExtendedEventArgsSet))
52✔
236
            return;
48✔
237

238
        var type = mapsDirectlyToDatabaseTable.GetType();
4✔
239

240
        foreach (var e in changedExtendedEventArgsSet.ToArray()) //call ToArray to avoid cyclical events on SetValue
16✔
241
        {
242
            var prop = type.GetProperty(e.PropertyName);
4✔
243
            prop.SetValue(mapsDirectlyToDatabaseTable, e.OldValue); //reset the old values
4✔
244
        }
245

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

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

257
        //if it has no changes (since a save)
258
        if (!_propertyChanges.TryGetValue(mapsDirectlyToDatabaseTable, out var changedExtendedEventArgsSet))
222!
259
            return new RevertableObjectReport { Evaluation = ChangeDescription.NoChanges };
222✔
260

261
        //we have local 'unsaved' changes
UNCOV
262
        var type = mapsDirectlyToDatabaseTable.GetType();
×
NEW
263
        var differences = changedExtendedEventArgsSet.Select(
×
UNCOV
264
                d => new RevertablePropertyDifference(type.GetProperty(d.PropertyName), d.NewValue, d.OldValue))
×
UNCOV
265
            .ToList();
×
266

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

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

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

279
        if (obj1 == null && obj2 == null)
261,396!
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;
261,396✔
284
    }
285

286
    /// <inheritdoc/>
287
    public int GetHashCode(IMapsDirectlyToDatabaseTable obj1) => obj1.GetType().GetHashCode() * obj1.ID;
199,867✔
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);
274✔
298

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

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

311
    public IEnumerable<T> GetAllObjectsInIDList<T>(IEnumerable<int> ids) where T : IMapsDirectlyToDatabaseTable
312
    {
313
        var hs = new HashSet<int>(ids);
16✔
314
        return Objects.Keys.OfType<T>().Where(o => hs.Contains(o.ID)).OrderBy(o => o.ID);
416✔
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);
926✔
327
        prop.SetValue(entity, propertyValue);
926✔
328
        SaveToDatabase(entity);
926✔
329
    }
926✔
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);
332✔
338

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

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

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

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

222,656✔
361
                        //or with a spontaneous base class
222,656✔
362
                        && (t.BaseType == null || !t.BaseType.Name.Contains("Spontaneous"))
222,656✔
363
                ).ToArray();
98✔
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