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

HicServices / RDMP / 9859858140

09 Jul 2024 03:24PM UTC coverage: 56.679% (-0.2%) from 56.916%
9859858140

push

github

JFriel
update

10912 of 20750 branches covered (52.59%)

Branch coverage included in aggregate %.

30965 of 53135 relevant lines covered (58.28%)

7908.05 hits per line

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

86.61
/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;
22✔
28

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

35
    private readonly ConcurrentDictionary<IMapsDirectlyToDatabaseTable, HashSet<PropertyChangedExtendedEventArgs>>
8,404✔
36
        _propertyChanges = new();
8,404✔
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++;
10,122✔
46
        toCreate.ID = NextObjectId;
10,122✔
47

48
        foreach (var kvp in constructorParameters)
72,724✔
49
        {
50
            var val = kvp.Value;
26,240✔
51

52
            //don't set nulls
53
            if (val == DBNull.Value)
26,240✔
54
                val = null;
2,436✔
55

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

58
            var strVal = kvp.Value as string;
26,240✔
59

60
            SetValue(toCreate, prop, strVal, val);
26,240✔
61
        }
62

63
        toCreate.Repository = this;
10,122✔
64

65
        Objects.TryAdd(toCreate, 0);
10,122✔
66

67
        toCreate.PropertyChanged += toCreate_PropertyChanged;
10,122✔
68

69
        NewObjectPool.Add(toCreate);
10,122✔
70

71
        Inserting?.Invoke(this, new IMapsDirectlyToDatabaseTableEventArgs(toCreate));
10,122!
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)
26,240✔
78
        {
79
            prop.SetValue(toCreate, null);
2,644✔
80
            return;
2,644✔
81
        }
82

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

86
        if (type.IsEnum)
23,596✔
87
        {
88
            prop.SetValue(toCreate, strVal != null ? Enum.Parse(type, strVal) : Enum.ToObject(type, val));
1,588✔
89
        }
90
        else
91
            prop.SetValue(toCreate, Convert.ChangeType(val, type));
22,008✔
92
    }
22,008✔
93

94
    private void toCreate_PropertyChanged(object sender, PropertyChangedEventArgs e)
95
    {
96
        var changes = (PropertyChangedExtendedEventArgs)e;
4,634✔
97
        var onObject = (IMapsDirectlyToDatabaseTable)sender;
4,634✔
98

99
        //if we don't know about this object yet
100
        _propertyChanges.TryAdd(onObject, new HashSet<PropertyChangedExtendedEventArgs>());
4,634✔
101

102
        //if we already knew of a previous change
103
        var collision = _propertyChanges[onObject].SingleOrDefault(c => c.PropertyName.Equals(changes.PropertyName));
7,894✔
104

105
        //throw away that knowledge
106
        if (collision != null)
4,634✔
107
            _propertyChanges[onObject].Remove(collision);
56✔
108

109
        //we know about this change now
110
        _propertyChanges[onObject].Add(changes);
4,634✔
111
    }
4,634✔
112

113

114
    public T GetObjectByID<T>(int id) where T : IMapsDirectlyToDatabaseTable
115
    {
116
        if (id == 0)
4,144!
117
            return default;
×
118

119
        try
120
        {
121
            return Objects.Keys.OfType<T>().Single(o => o.ID == id);
55,296✔
122
        }
123
        catch (InvalidOperationException e)
8✔
124
        {
125
            throw new KeyNotFoundException($"Could not find {typeof(T).Name} with ID {id}", e);
8✔
126
        }
127
    }
4,136✔
128

129
    public T[] GetAllObjects<T>() where T : IMapsDirectlyToDatabaseTable
130
    {
131
        return Objects.Keys.OfType<T>().OrderBy(o => o.ID).ToArray();
78,502✔
132
    }
133

134
    public T[] GetAllObjectsWhere<T>(string property, object value1) where T : IMapsDirectlyToDatabaseTable
135
    {
136
        var prop = typeof(T).GetProperty(property);
1,950✔
137

138
        return GetAllObjects<T>().Where(o => Equals(prop.GetValue(o), value1)).ToArray();
4,800✔
139
    }
140

141
    public T[] GetAllObjectsWhere<T>(string property1, object value1, ExpressionType operand, string property2,
142
        object value2) where T : IMapsDirectlyToDatabaseTable
143
    {
144
        var prop1 = typeof(T).GetProperty(property1);
238✔
145
        var prop2 = typeof(T).GetProperty(property2);
238✔
146

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

159
    public IEnumerable<IMapsDirectlyToDatabaseTable> GetAllObjects(Type t)
160
    {
161
        return Objects.Keys.Where(o => o.GetType() == t);
2,302✔
162
    }
163

164
    public T[] GetAllObjectsWithParent<T>(IMapsDirectlyToDatabaseTable parent) where T : IMapsDirectlyToDatabaseTable
165
    {
166
        //e.g. Catalogue_ID
167
        var propertyName = $"{parent.GetType().Name}_ID";
3,560✔
168

169
        var prop = typeof(T).GetProperty(propertyName);
3,560✔
170
        return Objects.Keys.OfType<T>().Where(o => prop.GetValue(o) as int? == parent.ID).Cast<T>().OrderBy(o => o.ID)
36,200✔
171
            .ToArray();
3,560✔
172
    }
173

174
    public T[] GetAllObjectsWithParent<T, T2>(T2 parent) where T : IMapsDirectlyToDatabaseTable, IInjectKnown<T2>
175
        where T2 : IMapsDirectlyToDatabaseTable
176
    {
177
        //e.g. Catalogue_ID
178
        var propertyName = $"{typeof(T2).Name}_ID";
1,240✔
179

180
        var prop = typeof(T).GetProperty(propertyName);
1,240✔
181
        return Objects.Keys.OfType<T>().Where(o => prop.GetValue(o) as int? == parent.ID).Cast<T>().OrderBy(o => o.ID)
27,562✔
182
            .ToArray();
1,240✔
183
    }
184

185
    public virtual void SaveToDatabase(IMapsDirectlyToDatabaseTable oTableWrapperObject)
186
    {
187
        Saving?.Invoke(this, new SaveEventArgs(oTableWrapperObject));
4,968!
188

189
        var existing = Objects.Keys.FirstOrDefault(k => k.Equals(oTableWrapperObject));
274,454✔
190

191
        // If saving a new reference to an existing object then we should update our tracked
192
        // objects to the latest reference since the old one is stale
193
        if (!ReferenceEquals(existing, oTableWrapperObject))
4,968✔
194
        {
195
            Objects.TryRemove(oTableWrapperObject, out _);
2✔
196
            Objects.TryAdd(oTableWrapperObject, 0);
2✔
197
        }
198

199
        //forget about property changes (since it's 'saved' now)
200
        _propertyChanges.TryRemove(oTableWrapperObject, out _);
4,968✔
201
    }
4,968✔
202

203
    public virtual void DeleteFromDatabase(IMapsDirectlyToDatabaseTable oTableWrapperObject)
204
    {
205
        CascadeDeletes(oTableWrapperObject);
510✔
206

207
        Objects.TryRemove(oTableWrapperObject, out _);
510✔
208

209
        //forget about property changes (since it's been deleted)
210
        _propertyChanges.TryRemove(oTableWrapperObject, out _);
510✔
211

212
        Deleting?.Invoke(this, new IMapsDirectlyToDatabaseTableEventArgs(oTableWrapperObject));
510!
213
    }
×
214

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

224
    public void RevertToDatabaseState([NotNull] IMapsDirectlyToDatabaseTable mapsDirectlyToDatabaseTable)
225
    {
226
        //Mark any cached data as out of date
227
        if (mapsDirectlyToDatabaseTable is IInjectKnown inject)
68✔
228
            inject.ClearAllInjections();
38✔
229

230
        if (!_propertyChanges.TryGetValue(mapsDirectlyToDatabaseTable, out var changedExtendedEventArgsSet))
68✔
231
            return;
58✔
232

233
        var type = mapsDirectlyToDatabaseTable.GetType();
10✔
234

235
        foreach (var e in changedExtendedEventArgsSet.ToArray()) //call ToArray to avoid cyclical events on SetValue
40✔
236
        {
237
            var prop = type.GetProperty(e.PropertyName);
10✔
238
            prop.SetValue(mapsDirectlyToDatabaseTable, e.OldValue); //reset the old values
10✔
239
        }
240

241
        //forget about all changes now
242
        _propertyChanges.TryRemove(mapsDirectlyToDatabaseTable, out _);
10✔
243
    }
10✔
244

245
    [NotNull]
246
    public RevertableObjectReport HasLocalChanges(IMapsDirectlyToDatabaseTable mapsDirectlyToDatabaseTable)
247
    {
248
        //if we don't know about it then it was deleted
249
        if (!Objects.ContainsKey(mapsDirectlyToDatabaseTable))
250!
250
            return new RevertableObjectReport { Evaluation = ChangeDescription.DatabaseCopyWasDeleted };
×
251

252
        //if it has no changes (since a save)
253
        if (!_propertyChanges.TryGetValue(mapsDirectlyToDatabaseTable, out var changedExtendedEventArgsSet))
250✔
254
            return new RevertableObjectReport { Evaluation = ChangeDescription.NoChanges };
240✔
255

256
        //we have local 'unsaved' changes
257
        var type = mapsDirectlyToDatabaseTable.GetType();
10✔
258
        var differences = changedExtendedEventArgsSet.Select(
10✔
259
                d => new RevertablePropertyDifference(type.GetProperty(d.PropertyName), d.NewValue, d.OldValue))
10✔
260
            .ToList();
10✔
261

262
        return new RevertableObjectReport(differences) { Evaluation = ChangeDescription.DatabaseCopyDifferent };
10✔
263
    }
264

265
    /// <inheritdoc/>
266
    public bool AreEqual(IMapsDirectlyToDatabaseTable obj1, object obj2)
267
    {
268
        if (obj1 == null && obj2 != null)
353,231!
269
            return false;
×
270

271
        if (obj2 == null && obj1 != null)
353,231✔
272
            return false;
2✔
273

274
        if (obj1 == null && obj2 == null)
353,229!
275
            throw new NotSupportedException(
×
276
                "Why are you comparing two null things against one another with this method?");
×
277

278
        return obj1.GetType() == obj2.GetType() && obj1.ID == ((IMapsDirectlyToDatabaseTable)obj2).ID;
353,229✔
279
    }
280

281
    /// <inheritdoc/>
282
    public int GetHashCode(IMapsDirectlyToDatabaseTable obj1) => obj1.GetType().GetHashCode() * obj1.ID;
226,066✔
283

284
    public Version GetVersion() => GetType().Assembly.GetName().Version;
×
285

286

287
    public bool StillExists<T>(int allegedParent) where T : IMapsDirectlyToDatabaseTable
288
    {
289
        return Objects.Keys.OfType<T>().Any(o => o.ID == allegedParent);
×
290
    }
291

292
    public bool StillExists(IMapsDirectlyToDatabaseTable o) => Objects.ContainsKey(o);
310✔
293

294
    public bool StillExists(Type objectType, int objectId)
295
    {
296
        return Objects.Keys.Any(o => o.GetType() == objectType && o.ID == objectId);
2,848✔
297
    }
298

299
    public IMapsDirectlyToDatabaseTable GetObjectByID(Type objectType, int objectId)
300
    {
301
        return Objects.Keys.SingleOrDefault(o => o.GetType() == objectType && objectId == o.ID)
1,938✔
302
               ?? throw new KeyNotFoundException(
92✔
303
                   $"Could not find object of Type '{objectType}' with ID '{objectId}' in {nameof(MemoryRepository)}");
92✔
304
    }
305

306
    public IEnumerable<T> GetAllObjectsInIDList<T>(IEnumerable<int> ids) where T : IMapsDirectlyToDatabaseTable
307
    {
308
        var hs = new HashSet<int>(ids);
18✔
309
        return Objects.Keys.OfType<T>().Where(o => hs.Contains(o.ID)).OrderBy(o => o.ID);
540✔
310
    }
311

312
    public IEnumerable<IMapsDirectlyToDatabaseTable> GetAllObjectsInIDList(Type elementType, IEnumerable<int> ids)
313
    {
314
        var hs = new HashSet<int>(ids);
16✔
315
        return GetAllObjects(elementType).Where(o => hs.Contains(o.ID));
102✔
316
    }
317

318
    public void SaveSpecificPropertyOnlyToDatabase(IMapsDirectlyToDatabaseTable entity, string propertyName,
319
        object propertyValue)
320
    {
321
        var prop = entity.GetType().GetProperty(propertyName);
1,042✔
322
        prop.SetValue(entity, propertyValue);
1,042✔
323
        SaveToDatabase(entity);
1,042✔
324
    }
1,042✔
325

326

327
    public IMapsDirectlyToDatabaseTable[] GetAllObjectsInDatabase()
328
    {
329
        return Objects.Keys.OrderBy(o => o.ID).ToArray();
106✔
330
    }
331

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

334
    public void TestConnection()
335
    {
336
    }
×
337

338
    public virtual void Clear()
339
    {
340
        Objects.Clear();
2,038✔
341
    }
2,038✔
342

343
    public Type[] GetCompatibleTypes()
344
    {
345
        return
98✔
346
            GetType().Assembly.GetTypes()
98✔
347
                .Where(
98✔
348
                    t =>
98✔
349
                        typeof(IMapsDirectlyToDatabaseTable).IsAssignableFrom(t)
228,242!
350
                        && !t.IsAbstract
228,242✔
351
                        && !t.IsInterface
228,242✔
352

228,242✔
353
                        //nothing called spontaneous
228,242✔
354
                        && !t.Name.Contains("Spontaneous")
228,242✔
355

228,242✔
356
                        //or with a spontaneous base class
228,242✔
357
                        && (t.BaseType == null || !t.BaseType.Name.Contains("Spontaneous"))
228,242✔
358
                ).ToArray();
98✔
359
    }
360

361

362
    public IDisposable BeginNewTransaction() => new EmptyDisposeable();
×
363

364
    public void EndTransaction(bool commit)
365
    {
366
    }
×
367
}
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