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

realm / realm-dotnet / 5716220196

31 Jul 2023 02:52PM UTC coverage: 82.478% (-0.3%) from 82.741%
5716220196

Pull #3261

github

aed2eb
fealebenpae
Merge remote-tracking branch 'origin/main' into yg/updated-marshaling

# Conflicts:
#	Realm/Realm/Handles/SharedRealmHandle.cs
Pull Request #3261: Use modern-er marshaling techniques

2029 of 2601 branches covered (78.01%)

Branch coverage included in aggregate %.

201 of 201 new or added lines in 21 files covered. (100.0%)

6246 of 7432 relevant lines covered (84.04%)

34048.54 hits per line

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

78.95
/Realm/Realm/Schema/ObjectSchema.cs
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2016 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
using System;
20
using System.Collections;
21
using System.Collections.Concurrent;
22
using System.Collections.Generic;
23
using System.Collections.ObjectModel;
24
using System.Diagnostics;
25
using System.Linq;
26
using System.Reflection;
27
using Realms.Helpers;
28
using Realms.Native;
29

30
namespace Realms.Schema
31
{
32
    /// <summary>
33
    /// Public description of a class stored in a Realm, as a collection of managed Property objects. To construct
34
    /// a new instance, use the <see cref="Builder">ObjectSchema.Builder</see> API.
35
    /// </summary>
36
    [DebuggerDisplay("Name = {Name}, Properties = {Count}")]
37
    public class ObjectSchema : IReadOnlyCollection<Property>
38
    {
39
        /// <summary>
40
        /// Represents the object schema type of an <see cref="ObjectSchema"/>.
41
        /// </summary>
42
        public enum ObjectType : byte
43
        {
44
            /// <summary>
45
            /// The value represents a <see cref="RealmObject"/> schema type.
46
            /// </summary>
47
            RealmObject = 0,
48

49
            /// <summary>
50
            /// The value represents a <see cref="EmbeddedObject"/> schema type.
51
            /// </summary>
52
            EmbeddedObject = 1,
53

54
            /// <summary>
55
            /// The value represents a <see cref="AsymmetricObject"/> schema type.
56
            /// </summary>
57
            AsymmetricObject = 2,
58
        }
59

60
        private static readonly ConcurrentDictionary<Type, ObjectSchema> _cache = new();
1✔
61

62
        private readonly ReadOnlyDictionary<string, Property> _properties;
63

64
        /// <summary>
65
        /// Gets the name of the original class declaration from which the schema was built.
66
        /// </summary>
67
        /// <value>The name of the class.</value>
68
        public string Name { get; }
1,135,367✔
69

70
        /// <summary>
71
        /// Gets the number of properties in the schema, which is the persistent properties from the original class.
72
        /// </summary>
73
        /// <value>The number of persistent properties for the object.</value>
74
        public int Count => _properties.Count;
278,447✔
75

76
        internal Property? PrimaryKeyProperty { get; }
284,988✔
77

78
        internal Type? Type { get; private set; }
558,137✔
79

80
        /// <summary>
81
        /// Gets a <see cref="ObjectType"/> indicating whether this <see cref="ObjectSchema"/> describes
82
        /// a top level object, an embedded object or an asymmetric object.
83
        /// </summary>
84
        /// <value>The type of ObjectSchema.</value>
85
        public ObjectType BaseType { get; }
294,738✔
86

87
        private ObjectSchema(string name, ObjectType schemaType, IDictionary<string, Property> properties)
973✔
88
        {
89
            Name = name;
973✔
90
            BaseType = schemaType;
973✔
91

92
            _properties = new ReadOnlyDictionary<string, Property>(properties);
973✔
93

94
            foreach (var kvp in _properties.Where(kvp => kvp.Value.IsPrimaryKey))
7,555✔
95
            {
96
                if (PrimaryKeyProperty != null)
367✔
97
                {
98
                    var pkProperties = _properties.Where(p => p.Value.IsPrimaryKey).Select(p => p.Value.Name);
5✔
99
                    throw new ArgumentException($"This schema already contains more than one property that is designated as primary key: {string.Join(",", pkProperties)}", nameof(properties));
1✔
100
                }
101

102
                PrimaryKeyProperty = kvp.Value;
366✔
103
            }
104
        }
972✔
105

106
        internal ObjectSchema(in Native.SchemaObject native)
×
107
        {
108
            Name = native.name!;
×
109
            BaseType = native.table_type;
×
110
            _properties = new(native.properties.ToEnumerable().ToDictionary(p => (string)p.name!, p => new Property(p)));
×
111
            if (native.primary_key)
×
112
            {
113
                PrimaryKeyProperty = _properties[native.primary_key!];
×
114
            }
115
        }
×
116

117
        /// <summary>
118
        /// Looks for a <see cref="Property"/> by <see cref="Property.Name"/>.
119
        /// Failure to find means it is not regarded as a property to persist in a <see cref="Realm"/>.
120
        /// </summary>
121
        /// <returns><c>true</c>, if a <see cref="Property"/> was found matching <see cref="Property.Name"/>;
122
        /// <c>false</c> otherwise.</returns>
123
        /// <param name="name"><see cref="Property.Name"/> of the <see cref="Property"/> to match exactly.</param>
124
        /// <param name="property"><see cref="Property"/> returned only if found matching Name.</param>
125
        public bool TryFindProperty(string name, out Property property)
126
        {
127
            Argument.NotNullOrEmpty(name, nameof(name));
135,831✔
128

129
            return _properties.TryGetValue(name, out property);
135,829✔
130
        }
131

132
        /// <summary>
133
        /// Create a mutable <see cref="Builder"/> containing the properties in this schema.
134
        /// </summary>
135
        /// <returns>
136
        /// A <see cref="Builder"/> instance that can be used to mutate the schema and eventually
137
        /// produce a new one by calling <see cref="Builder.Build"/>.
138
        /// </returns>
139
        public Builder GetBuilder()
140
        {
141
            var builder = new Builder(Name, BaseType);
5✔
142
            foreach (var prop in this)
20✔
143
            {
144
                builder.Add(prop);
5✔
145
            }
146

147
            builder.Type = Type;
5✔
148

149
            return builder;
5✔
150
        }
151

152
        /// <inheritdoc/>
153
        public IEnumerator<Property> GetEnumerator() => _properties.Values.GetEnumerator();
557,236✔
154

155
        /// <inheritdoc/>
156
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
37✔
157

158
        internal static ObjectSchema FromType(Type type)
159
        {
160
            Argument.NotNull(type, nameof(type));
5,616✔
161

162
            return _cache.GetOrAdd(type, t => new Builder(t).Build());
5,736✔
163
        }
164

165
        internal SchemaObject ToNative(Arena arena) => new()
278,529✔
166
        {
278,529✔
167
            name = StringValue.AllocateFrom(Name, arena),
278,529✔
168
            table_type = BaseType,
278,529✔
169
            properties = MarshaledVector<SchemaProperty>.AllocateFrom(this.Select(p => p.ToNative(arena)).ToArray(), arena),
1,524,260✔
170
            primary_key = StringValue.AllocateFrom(PrimaryKeyProperty?.Name, arena)
278,529✔
171
        };
278,529✔
172

173
        /// <summary>
174
        /// A mutable builder that allows you to construct an <see cref="ObjectSchema"/> instance.
175
        /// </summary>
176
        public class Builder : SchemaBuilderBase<Property>
177
        {
178
            internal Type? Type;
179

180
            /// <summary>
181
            /// Gets or sets the name of the class described by the builder.
182
            /// </summary>
183
            /// <value>The name of the class.</value>
184
            public string Name { get; set; }
1,957✔
185

186
            /// <summary>
187
            /// Gets or sets a value indicating the object's <see cref="ObjectType"/> this <see cref="Builder"/> describes.
188
            /// </summary>
189
            /// <value><see cref="ObjectType"/> of the schema of the object.</value>
190
            public ObjectType RealmSchemaType { get; set; }
1,959✔
191

192
            /// <summary>
193
            /// Initializes a new instance of the <see cref="Builder"/> class with the provided name.
194
            /// </summary>
195
            /// <param name="name">The name of the <see cref="ObjectSchema"/> this builder describes.</param>
196
            /// <param name="schemaType">The <see cref="ObjectType"/> of the object this builder describes.</param>
197
            /// <exception cref="ArgumentNullException">Thrown if <paramref name="name"/> is <c>null</c>.</exception>
198
            /// <exception cref="ArgumentException">Thrown if <paramref name="name"/> is the empty string.</exception>
199
            public Builder(string name, ObjectType schemaType = ObjectType.RealmObject)
857✔
200
            {
201
                Argument.NotNullOrEmpty(name, nameof(name));
857✔
202

203
                Name = name;
855✔
204
                RealmSchemaType = schemaType;
855✔
205
            }
855✔
206

207
            /// <summary>
208
            /// Initializes a new instance of the <see cref="Builder"/> class populated with properties from the
209
            /// provided <paramref name="type"/>.
210
            /// </summary>
211
            /// <param name="type">
212
            /// The <see cref="System.Type"/> that will be used to populate the builder. It must be a <see cref="RealmObject"/>,
213
            /// an <see cref="EmbeddedObject"/>, or an <see cref="AsymmetricObject"/> inheritor.
214
            /// </param>
215
            /// <remarks>
216
            /// If you want to use strongly typed API, such as <see cref="Realm.Add{T}(T, bool)">Realm.Add&lt;T&gt;</see> or
217
            /// <see cref="Realm.All{T}">Realm.All&lt;T&gt;</see>, you must use this method to build your schema.
218
            /// <br/>
219
            /// Adding new properties is fully supported, but removing or changing properties defined on the class will result
220
            /// in runtime errors being thrown if those properties are accessed via the object property accessors.
221
            /// </remarks>
222
            /// <example>
223
            /// <code>
224
            /// class Person : RealmObject
225
            /// {
226
            ///     public string Name { get; set; }
227
            /// }
228
            ///
229
            /// var personSchema = new Builder(typeof(Person));
230
            ///
231
            /// // someTagsCollection is a collection of tags determined at runtime - e.g. obtained
232
            /// // from a REST API.
233
            /// foreach (var tag in someTagsCollection)
234
            /// {
235
            ///     personSchema.Add(Property.Primitive(tag, RealmValueType.Bool));
236
            /// }
237
            ///
238
            /// var config = new RealmConfiguration
239
            /// {
240
            ///     Schema = new[] { personSchema.Build() }
241
            /// }
242
            /// using var realm = Realm.GetInstance(config);
243
            ///
244
            /// // Query for all people with a particular tag
245
            /// var tag = "Tall";
246
            /// var matches = realm.All&lt;Person&gt;().Filter($"{tag} = TRUE");
247
            ///
248
            /// // Get/set the tag of a particular person
249
            /// var hasTag = person.DynamicApi.Get&lt;bool&gt;(tag);
250
            /// person.DynamicApi.Set(tag, true);
251
            /// </code>
252
            /// </example>
253
            public Builder(Type type)
129✔
254
            {
255
                Argument.NotNull(type, nameof(type));
129✔
256
                Argument.Ensure(type.IsRealmObject() || type.IsEmbeddedObject() || type.IsAsymmetricObject(),
128✔
257
                    $"The class {type.FullName} must descend directly from either RealmObject, EmbeddedObject, or AsymmetricObject", nameof(type));
128✔
258

259
                RealmSchemaType = type.GetRealmSchemaType();
125✔
260

261
                var schemaField = type.GetField("RealmSchema", BindingFlags.Public | BindingFlags.Static);
125✔
262
                if (schemaField != null)
125!
263
                {
264
                    var objectSchema = (ObjectSchema)schemaField.GetValue(null)!;
125✔
265
                    Name = objectSchema.Name;
125✔
266

267
                    foreach (var prop in objectSchema)
1,600✔
268
                    {
269
                        Add(prop);
675✔
270
                    }
271
                }
272
                else
273
                {
274
                    Name = type.GetMappedOrOriginalName();
×
275
                    foreach (var property in type.GetTypeInfo().DeclaredProperties.Where(p => !p.IsStatic() && p.HasCustomAttribute<WovenPropertyAttribute>()))
×
276
                    {
277
                        Add(Property.FromPropertyInfo(property));
×
278
                    }
279
                }
280

281
                if (Count == 0)
125!
282
                {
283
                    throw new InvalidOperationException(
×
284
                        $"No properties in {type.Name}, has linker stripped it? See https://docs.mongodb.com/realm/sdk/dotnet/troubleshooting/#resolve-a--no-properties-in-class--exception");
×
285
                }
286

287
                Type = type;
125✔
288
            }
125✔
289

290
            /// <summary>
291
            /// Constructs an <see cref="ObjectSchema"/> from the properties added to this <see cref="Builder"/>.
292
            /// </summary>
293
            /// <returns>An immutable <see cref="ObjectSchema"/> instance that contains the properties added to the <see cref="Builder"/>.</returns>
294
            public ObjectSchema Build() => new(Name, RealmSchemaType, _values) { Type = Type };
973✔
295

296
            /// <summary>
297
            /// Adds a new <see cref="Property"/> to this <see cref="Builder"/>.
298
            /// </summary>
299
            /// <param name="item">The <see cref="Property"/> to add.</param>
300
            /// <returns>The original <see cref="Builder"/> instance to enable chaining multiple <see cref="Add(Property)"/> calls.</returns>
301
            public new Builder Add(Property item)
302
            {
303
                base.Add(item);
4,893✔
304

305
                return this;
4,891✔
306
            }
307

308
            protected override string GetKey(Property item) => item.Name;
4,905✔
309
        }
310
    }
311
}
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