Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MongoDBProvider.cs
1using MongoDB.Bson;
2using MongoDB.Bson.IO;
3using MongoDB.Bson.Serialization;
4using MongoDB.Driver;
5using System;
7using System.IO;
8using System.Reflection;
9using System.Runtime.ExceptionServices;
10using System.Threading;
11using System.Threading.Tasks;
12using System.Xml;
13using Waher.Events;
24
26{
31 {
32 private readonly Dictionary<string, IMongoCollection<BsonDocument>> collections = [];
33 private readonly Dictionary<Type, IObjectSerializer> serializers = [];
34 private readonly AutoResetEvent serializerAdded = new(false);
35 private MongoClient client;
36 private IMongoDatabase database;
37 private string id;
38 private string defaultCollectionName;
39 private string lastCollectionName = null;
40 private IMongoCollection<BsonDocument> lastCollection = null;
41 private IMongoCollection<BsonDocument> defaultCollection;
42
48 public MongoDBProvider(string DatabaseName, string DefaultCollectionName)
49 {
50 MongoClientSettings Settings = new();
51 this.Init(Settings, DatabaseName, DefaultCollectionName);
52 }
53
60 public MongoDBProvider(string HostName, string DatabaseName, string DefaultCollectionName)
61 {
62 MongoClientSettings Settings = new()
63 {
64 Server = new MongoServerAddress(HostName)
65 };
66
67 this.Init(Settings, DatabaseName, DefaultCollectionName);
68 }
69
77 public MongoDBProvider(string HostName, int Port, string DatabaseName, string DefaultCollectionName)
78 {
79 MongoClientSettings Settings = new()
80 {
81 Server = new MongoServerAddress(HostName, Port)
82 };
83
84 this.Init(Settings, DatabaseName, DefaultCollectionName);
85 }
86
93 public MongoDBProvider(MongoClientSettings Settings, string DatabaseName, string DefaultCollectionName)
94 {
95 this.Init(Settings, DatabaseName, DefaultCollectionName);
96 }
97
98 private void Init(MongoClientSettings Settings, string DatabaseName, string DefaultCollectionName)
99 {
100 this.id = Guid.NewGuid().ToString().Replace("-", string.Empty);
101 this.client = new MongoClient(Settings);
102 this.database = this.client.GetDatabase(DatabaseName);
103
104 this.defaultCollectionName = DefaultCollectionName;
105 this.defaultCollection = this.GetCollection(this.defaultCollectionName);
106
107 ConstructorInfo DefaultConstructor;
109
111 {
112 try
113 {
114 DefaultConstructor = Types.GetDefaultConstructor(T);
115 if (DefaultConstructor is null)
116 continue;
117
118 S = DefaultConstructor.Invoke(Types.NoParameters) as IObjectSerializer;
119 if (S is null)
120 continue;
121 }
122 catch (Exception)
123 {
124 continue;
125 }
126
127 this.serializers[S.ValueType] = S;
128 }
129
130 this.serializers[typeof(GenericObject)] = new GenericObjectSerializer(this, false);
131 this.serializers[typeof(object)] = new GenericObjectSerializer(this, true);
132 }
133
137 public string Id => this.id;
138
142 public int ObjectIdByteCount => 12;
143
149 public IMongoCollection<BsonDocument> GetCollection(string CollectionName)
150 {
151 IMongoCollection<BsonDocument> Result;
152
153 lock (this.collections)
154 {
155 if (CollectionName == this.lastCollectionName)
156 Result = this.lastCollection;
157 else
158 {
159 if (!this.collections.TryGetValue(CollectionName, out Result))
160 {
161 Result = this.database.GetCollection<BsonDocument>(CollectionName);
162 this.collections[CollectionName] = Result;
163 }
164
165 this.lastCollection = Result;
166 this.lastCollectionName = CollectionName;
167 }
168 }
169
170 return Result;
171 }
172
176 public MongoClient Client => this.client;
177
181 public string DefaultCollectionName => this.defaultCollectionName;
182
186 public IMongoCollection<BsonDocument> DefaultCollection => this.defaultCollection;
187
194 {
195 IObjectSerializer Result;
196 TypeInfo TI = Type.GetTypeInfo();
197
198 lock (this.collections)
199 {
200 if (this.serializers.TryGetValue(Type, out Result))
201 return Result;
202
203 if (TI.IsEnum)
204 Result = new EnumSerializer(Type);
205 else if (Type.IsArray)
206 {
207 Type ElementType = Type.GetElementType();
208 Type T = Waher.Runtime.Inventory.Types.GetType(typeof(ByteArraySerializer).FullName.Replace("ByteArray", "Array"));
209 Type SerializerType = T.MakeGenericType([ElementType]);
210 Result = (IObjectSerializer)Activator.CreateInstance(SerializerType, this);
211 }
212 else if (TI.IsGenericType)
213 {
214 Type GT = Type.GetGenericTypeDefinition();
215 if (GT == typeof(Nullable<>))
216 {
217 Type NullableType = Type.GenericTypeArguments[0];
218
219 if (NullableType.IsEnum)
220 Result = new Serialization.NullableTypes.NullableEnumSerializer(NullableType);
221 else
222 Result = null;
223 }
224 else
225 Result = null;
226 }
227 else
228 Result = null;
229
230 if (Result is not null)
231 {
232 this.serializers[Type] = Result;
233 this.serializerAdded.Set();
234
235 return Result;
236 }
237 }
238
239 try
240 {
241 Result = new ObjectSerializer(Type, this);
242
243 lock (this.collections)
244 {
245 this.serializers[Type] = Result;
246 this.serializerAdded.Set();
247 }
248 }
249 catch (FileLoadException ex)
250 {
251 // Serializer in the process of being generated from another task or thread.
252
253 while (true)
254 {
255 if (!this.serializerAdded.WaitOne(1000))
256 ExceptionDispatchInfo.Capture(ex).Throw();
257
258 lock (this.collections)
259 {
260 if (this.serializers.TryGetValue(Type, out Result))
261 return Result;
262 }
263 }
264 }
265
266 return Result;
267 }
268
275 {
276 return this.GetObjectSerializerEx(Object.GetType());
277 }
278
285 {
286 if (this.GetObjectSerializer(Type) is not ObjectSerializer Serializer)
287 throw new Exception("Objects of type " + Type.FullName + " must be embedded.");
288
289 return Serializer;
290 }
291
296 public async Task Insert(object Object)
297 {
298 ObjectSerializer Serializer = this.GetObjectSerializerEx(Object);
299 string CollectionName = Serializer.CollectionName(Object);
300 IMongoCollection<BsonDocument> Collection;
301
302 if (string.IsNullOrEmpty(CollectionName))
303 Collection = this.defaultCollection;
304 else
305 Collection = this.GetCollection(CollectionName);
306
307 if (Serializer.HasObjectIdField)
308 {
309 if (Serializer.HasObjectId(Object))
310 throw new Exception("Object already has an Object ID. If updating an object, use the Update method.");
311 else
312 await Serializer.GetObjectId(Object, true);
313 }
314 else
315 {
316 BsonDocument Doc = Object.ToBsonDocument(Object.GetType(), Serializer);
317 await Collection.InsertOneAsync(Doc);
318 }
319 }
320
325 public Task Insert(params object[] Objects)
326 {
327 return this.Insert((IEnumerable<object>)Objects);
328 }
329
334 public async Task Insert(IEnumerable<object> Objects)
335 {
336 Dictionary<string, KeyValuePair<IMongoCollection<BsonDocument>, LinkedList<BsonDocument>>> DocumentsPerCollection = [];
337 Type Type;
338 Type LastType = null;
339 ObjectSerializer Serializer = null;
340 string CollectionName;
341 string LastCollectionName = null;
342 IMongoCollection<BsonDocument> Collection;
343 IMongoCollection<BsonDocument> LastCollection = null;
344 LinkedList<BsonDocument> Documents = null;
345 BsonDocument Document;
346
347 foreach (object Object in Objects)
348 {
349 Type = Object.GetType();
350
351 if (Type != LastType)
352 {
353 Serializer = this.GetObjectSerializerEx(Type);
354 CollectionName = Serializer.CollectionName(Object);
355 LastType = Type;
356
357 if (CollectionName == LastCollectionName)
358 Collection = LastCollection;
359 else
360 {
361 LastCollectionName = CollectionName;
362
363 if (string.IsNullOrEmpty(CollectionName))
364 CollectionName = this.defaultCollectionName;
365
366 if (DocumentsPerCollection.TryGetValue(CollectionName, out KeyValuePair<IMongoCollection<BsonDocument>, LinkedList<BsonDocument>> P))
367 Collection = P.Key;
368 else
369 {
370 Collection = this.GetCollection(CollectionName);
371 P = new KeyValuePair<IMongoCollection<BsonDocument>, LinkedList<BsonDocument>>(Collection, new LinkedList<BsonDocument>());
372 DocumentsPerCollection[CollectionName] = P;
373 }
374
375 Documents = P.Value;
376 LastCollection = Collection;
377 }
378 }
379
380 Document = Object.ToBsonDocument(Type, Serializer);
381 Documents.AddLast(Document);
382 }
383
384 foreach (KeyValuePair<IMongoCollection<BsonDocument>, LinkedList<BsonDocument>> P2 in DocumentsPerCollection.Values)
385 await P2.Key.InsertManyAsync(P2.Value);
386 }
387
393 public Task InsertLazy(object Object, ObjectCallback Callback)
394 => Process(Object, this.Insert(Object), Callback);
395
401 public Task InsertLazy(object[] Objects, ObjectsCallback Callback)
402 => Process(Objects, this.Insert(Objects), Callback);
403
409 public Task InsertLazy(IEnumerable<object> Objects, ObjectsCallback Callback)
410 => Process(Objects, this.Insert(Objects), Callback);
411
421 public Task<IEnumerable<T>> Find<T>(int Offset, int MaxCount, params string[] SortOrder)
422 where T : class
423 {
424 return this.Find<T>(Offset, MaxCount, (Filter)null, SortOrder);
425 }
426
437 public Task<IEnumerable<T>> Find<T>(int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
438 where T : class
439 {
440 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
441 string CollectionName = Serializer.CollectionName(null);
442 IMongoCollection<BsonDocument> Collection;
443 FilterDefinition<BsonDocument> BsonFilter;
444
445 if (string.IsNullOrEmpty(CollectionName))
446 Collection = this.defaultCollection;
447 else
448 Collection = this.GetCollection(CollectionName);
449
450 if (Filter is null)
451 BsonFilter = new BsonDocument();
452 else
453 BsonFilter = Convert(Filter, Serializer);
454
455 return Find<T>(Serializer, Collection, Offset, MaxCount, BsonFilter, null, SortOrder);
456 }
457
469 public Task<IEnumerable<T>> Find<T>(int Offset, int MaxCount, Filter Filter,
470 T ContinueAfter, params string[] SortOrder)
471 where T : class
472 {
473 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
474 string CollectionName = Serializer.CollectionName(null);
475 IMongoCollection<BsonDocument> Collection;
476 FilterDefinition<BsonDocument> BsonFilter;
477
478 if (string.IsNullOrEmpty(CollectionName))
479 Collection = this.defaultCollection;
480 else
481 Collection = this.GetCollection(CollectionName);
482
483 if (Filter is null)
484 BsonFilter = new BsonDocument();
485 else
486 BsonFilter = Convert(Filter, Serializer);
487
488 return Find(Serializer, Collection, Offset, MaxCount, BsonFilter, ContinueAfter, SortOrder);
489 }
490
501 public Task<IEnumerable<T>> Find<T>(int Offset, int MaxCount, FilterDefinition<BsonDocument> BsonFilter,
502 params string[] SortOrder)
503 where T : class
504 {
505 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
506 string CollectionName = Serializer.CollectionName(null);
507 IMongoCollection<BsonDocument> Collection;
508
509 if (string.IsNullOrEmpty(CollectionName))
510 Collection = this.defaultCollection;
511 else
512 Collection = this.GetCollection(CollectionName);
513
514 return Find<T>(Serializer, Collection, Offset, MaxCount, BsonFilter, null, SortOrder);
515 }
516
527 public Task<IEnumerable<T>> Find<T>(string CollectionName, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
528 where T : class
529 {
530 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
531 IMongoCollection<BsonDocument> Collection;
532 FilterDefinition<BsonDocument> BsonFilter;
533
534 if (string.IsNullOrEmpty(CollectionName))
535 Collection = this.defaultCollection;
536 else
537 Collection = this.GetCollection(CollectionName);
538
539 if (Filter is null)
540 BsonFilter = new BsonDocument();
541 else
542 BsonFilter = Convert(Filter, Serializer);
543
544 return Find<T>(Serializer, Collection, Offset, MaxCount, BsonFilter, null, SortOrder);
545 }
546
558 public Task<IEnumerable<T>> Find<T>(string CollectionName, int Offset, int MaxCount, Filter Filter,
559 T ContinueAfter, params string[] SortOrder)
560 where T : class
561 {
562 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
563 IMongoCollection<BsonDocument> Collection;
564 FilterDefinition<BsonDocument> BsonFilter;
565
566 if (string.IsNullOrEmpty(CollectionName))
567 Collection = this.defaultCollection;
568 else
569 Collection = this.GetCollection(CollectionName);
570
571 if (Filter is null)
572 BsonFilter = new BsonDocument();
573 else
574 BsonFilter = Convert(Filter, Serializer);
575
576 return Find(Serializer, Collection, Offset, MaxCount, BsonFilter, ContinueAfter, SortOrder);
577 }
578
588 public Task<IEnumerable<object>> Find(string CollectionName, int Offset, int MaxCount, params string[] SortOrder)
589 {
590 return this.Find(CollectionName, Offset, MaxCount, null, SortOrder);
591 }
592
603 public Task<IEnumerable<object>> Find(string CollectionName, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
604 {
605 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(object));
606 IMongoCollection<BsonDocument> Collection;
607 FilterDefinition<BsonDocument> BsonFilter;
608
609 if (string.IsNullOrEmpty(CollectionName))
610 Collection = this.defaultCollection;
611 else
612 Collection = this.GetCollection(CollectionName);
613
614 if (Filter is null)
615 BsonFilter = new BsonDocument();
616 else
617 BsonFilter = Convert(Filter, Serializer);
618
619 return Find<object>(Serializer, Collection, Offset, MaxCount, BsonFilter, SortOrder);
620 }
621
622 private static async Task<IEnumerable<T>> Find<T>(ObjectSerializer Serializer, IMongoCollection<BsonDocument> Collection,
623 int Offset, int MaxCount, FilterDefinition<BsonDocument> BsonFilter, T ContinueAfter, params string[] SortOrder)
624 where T : class
625 {
626 if (ContinueAfter is not null)
627 throw new NotImplementedException("Paginated searches not implemented in MongoDB provider.");
628
629 IFindFluent<BsonDocument, BsonDocument> ResultSet = Collection.Find(BsonFilter);
630
631 if (SortOrder.Length > 0)
632 {
633 SortDefinition<BsonDocument> SortDefinition = null;
634
635 foreach (string SortBy in SortOrder)
636 {
637 if (SortDefinition is null)
638 {
639 if (SortBy.StartsWith('-'))
640 SortDefinition = Builders<BsonDocument>.Sort.Descending(Serializer.ToShortName(SortBy[1..]));
641 else
642 SortDefinition = Builders<BsonDocument>.Sort.Ascending(Serializer.ToShortName(SortBy));
643 }
644 else
645 {
646 if (SortBy.StartsWith('-'))
647 SortDefinition = SortDefinition.Descending(Serializer.ToShortName(SortBy[1..]));
648 else
649 SortDefinition = SortDefinition.Ascending(Serializer.ToShortName(SortBy));
650 }
651 }
652
653 ResultSet = ResultSet.Sort(SortDefinition);
654 }
655
656 if (Offset > 0)
657 ResultSet = ResultSet.Skip(Offset);
658
659 if (MaxCount < int.MaxValue)
660 ResultSet = ResultSet.Limit(MaxCount);
661
662 IAsyncCursor<BsonDocument> Cursor = await ResultSet.ToCursorAsync();
663 LinkedList<T> Result = new();
664 BsonDeserializationArgs Args = new()
665 {
666 NominalType = typeof(T)
667 };
668
669 while (await Cursor.MoveNextAsync())
670 {
671 foreach (BsonDocument Document in Cursor.Current)
672 {
673 BsonDocumentReader Reader = new(Document);
674 BsonDeserializationContext Context = BsonDeserializationContext.CreateRoot(Reader);
675
676 if (Serializer.Deserialize(Context, Args) is T Obj)
677 Result.AddLast(Obj);
678 }
679 }
680
681 return Result;
682 }
683
684 internal static FilterDefinition<BsonDocument> Convert(Filter Filter, ObjectSerializer Serializer)
685 {
687 {
688 Filter[] ChildFilters = FilterChildren.ChildFilters;
689 int i, c = ChildFilters.Length;
690 FilterDefinition<BsonDocument>[] Children = new FilterDefinition<BsonDocument>[c];
691
692 for (i = 0; i < c; i++)
693 Children[i] = Convert(ChildFilters[i], Serializer);
694
695 if (Filter is FilterAnd)
696 return Builders<BsonDocument>.Filter.And(Children);
697 else if (Filter is FilterOr)
698 return Builders<BsonDocument>.Filter.Or(Children);
699 else
700 throw UnknownFilterType(Filter);
701 }
702 else if (Filter is FilterChild FilterChild)
703 {
704 FilterDefinition<BsonDocument> Child = Convert(FilterChild.ChildFilter, Serializer);
705
706 if (Filter is FilterNot)
707 return Builders<BsonDocument>.Filter.Not(Child);
708 else
709 throw UnknownFilterType(Filter);
710 }
712 {
713 object Value = FilterFieldValue.Value;
714 string FieldName = Serializer.ToShortName(FilterFieldValue.FieldName, ref Value);
715 bool HasType = Serializer.TryGetFieldType(FilterFieldValue.FieldName, null, out Type FieldType);
716 bool IsDefaultValue = Serializer.IsDefaultValue(FilterFieldValue.FieldName, Value);
717
719 {
720 if (IsDefaultValue)
721 return Builders<BsonDocument>.Filter.Eq<string>(FieldName, null);
722 else if (Value is string s)
723 {
724 if (HasType && FieldType == typeof(CaseInsensitiveString))
725 return Builders<BsonDocument>.Filter.Eq<string>(FieldName + "_L", s);
726 else
727 return Builders<BsonDocument>.Filter.Eq<string>(FieldName, s);
728 }
729 else if (Value is CaseInsensitiveString cis)
730 return Builders<BsonDocument>.Filter.Eq<string>(FieldName + "_L", cis.LowerCase);
731 else if (Value is sbyte i8)
732 return Builders<BsonDocument>.Filter.Eq<int>(FieldName, i8);
733 else if (Value is short i16)
734 return Builders<BsonDocument>.Filter.Eq<int>(FieldName, i16);
735 else if (Value is int i32)
736 return Builders<BsonDocument>.Filter.Eq<int>(FieldName, i32);
737 else if (Value is long i64)
738 return Builders<BsonDocument>.Filter.Eq<long>(FieldName, i64);
739 else if (Value is byte ui8)
740 return Builders<BsonDocument>.Filter.Eq<int>(FieldName, ui8);
741 else if (Value is ushort ui16)
742 return Builders<BsonDocument>.Filter.Eq<int>(FieldName, ui16);
743 else if (Value is uint ui32)
744 return Builders<BsonDocument>.Filter.Eq<long>(FieldName, ui32);
745 else if (Value is ulong ui64)
746 return Builders<BsonDocument>.Filter.Eq<Decimal128>(FieldName, ui64);
747 else if (Value is double d)
748 return Builders<BsonDocument>.Filter.Eq<double>(FieldName, d);
749 else if (Value is float f)
750 return Builders<BsonDocument>.Filter.Eq<double>(FieldName, f);
751 else if (Value is decimal d2)
752 return Builders<BsonDocument>.Filter.Eq<Decimal128>(FieldName, d2);
753 else if (Value is bool b)
754 return Builders<BsonDocument>.Filter.Eq<bool>(FieldName, b);
755 else if (Value is DateTime DT)
756 return Builders<BsonDocument>.Filter.Eq<long>(FieldName, (long)(DT - ObjectSerializer.UnixEpoch).TotalMilliseconds);
757 else if (Value is DateTimeOffset DTO)
758 {
759 return Builders<BsonDocument>.Filter.And(
760 Builders<BsonDocument>.Filter.Eq<long>(FieldName + ".tp", (long)(DTO.DateTime - ObjectSerializer.UnixEpoch).TotalMilliseconds),
761 Builders<BsonDocument>.Filter.Eq<string>(FieldName + ".tz", DTO.Offset.ToString()));
762 }
763 else if (Value is TimeSpan TS)
764 return Builders<BsonDocument>.Filter.Eq<string>(FieldName, TS.ToString());
765 else if (Value is Guid Guid)
766 return Builders<BsonDocument>.Filter.Eq<string>(FieldName, Guid.ToString());
767 else if (Value is ObjectId ObjectId)
768 return Builders<BsonDocument>.Filter.Eq<ObjectId>(FieldName, ObjectId);
769 else
770 throw UnhandledFilterValueDataType(Serializer.ValueType.FullName, FieldName, Value);
771 }
772 else if (Filter is FilterFieldNotEqualTo)
773 {
774 if (IsDefaultValue)
775 return Builders<BsonDocument>.Filter.Ne<string>(FieldName, null);
776 else if (Value is string s)
777 {
778 if (HasType && FieldType == typeof(CaseInsensitiveString))
779 return Builders<BsonDocument>.Filter.Ne<string>(FieldName + "_L", s);
780 else
781 return Builders<BsonDocument>.Filter.Ne<string>(FieldName, s);
782 }
783 else if (Value is CaseInsensitiveString cis)
784 return Builders<BsonDocument>.Filter.Ne<string>(FieldName + "_L", cis.LowerCase);
785 else if (Value is sbyte i8)
786 return Builders<BsonDocument>.Filter.Ne<int>(FieldName, i8);
787 else if (Value is short i16)
788 return Builders<BsonDocument>.Filter.Ne<int>(FieldName, i16);
789 else if (Value is int i32)
790 return Builders<BsonDocument>.Filter.Ne<int>(FieldName, i32);
791 else if (Value is long i64)
792 return Builders<BsonDocument>.Filter.Ne<long>(FieldName, i64);
793 else if (Value is byte ui8)
794 return Builders<BsonDocument>.Filter.Ne<int>(FieldName, ui8);
795 else if (Value is ushort ui16)
796 return Builders<BsonDocument>.Filter.Ne<int>(FieldName, ui16);
797 else if (Value is uint ui32)
798 return Builders<BsonDocument>.Filter.Ne<long>(FieldName, ui32);
799 else if (Value is ulong ui64)
800 return Builders<BsonDocument>.Filter.Ne<Decimal128>(FieldName, ui64);
801 else if (Value is double d)
802 return Builders<BsonDocument>.Filter.Ne<double>(FieldName, d);
803 else if (Value is float f)
804 return Builders<BsonDocument>.Filter.Ne<double>(FieldName, f);
805 else if (Value is decimal d2)
806 return Builders<BsonDocument>.Filter.Ne<Decimal128>(FieldName, d2);
807 else if (Value is bool b)
808 return Builders<BsonDocument>.Filter.Ne<bool>(FieldName, b);
809 else if (Value is DateTime DT)
810 return Builders<BsonDocument>.Filter.Ne<long>(FieldName, (long)(DT - ObjectSerializer.UnixEpoch).TotalMilliseconds);
811 else if (Value is DateTimeOffset DTO)
812 {
813 return Builders<BsonDocument>.Filter.Or(
814 Builders<BsonDocument>.Filter.Ne<long>(FieldName + ".tp", (long)(DTO.DateTime - ObjectSerializer.UnixEpoch).TotalMilliseconds),
815 Builders<BsonDocument>.Filter.Ne<string>(FieldName + ".tz", DTO.Offset.ToString()));
816 }
817 else if (Value is TimeSpan TS)
818 return Builders<BsonDocument>.Filter.Ne<string>(FieldName, TS.ToString());
819 else if (Value is Guid Guid)
820 return Builders<BsonDocument>.Filter.Ne<string>(FieldName, Guid.ToString());
821 else if (Value is ObjectId ObjectId)
822 return Builders<BsonDocument>.Filter.Ne<ObjectId>(FieldName, ObjectId);
823 else
824 throw UnhandledFilterValueDataType(Serializer.ValueType.FullName, FieldName, Value);
825 }
826 else if (Filter is FilterFieldGreaterThan)
827 {
828 if (Value is string s)
829 {
830 if (HasType && FieldType == typeof(CaseInsensitiveString))
831 return Builders<BsonDocument>.Filter.Gt<string>(FieldName + "_L", s);
832 else
833 return Builders<BsonDocument>.Filter.Gt<string>(FieldName, s);
834 }
835 else if (Value is CaseInsensitiveString cis)
836 return Builders<BsonDocument>.Filter.Gt<string>(FieldName + "_L", cis.LowerCase);
837 else if (Value is sbyte i8)
838 return Builders<BsonDocument>.Filter.Gt<int>(FieldName, i8);
839 else if (Value is short i16)
840 return Builders<BsonDocument>.Filter.Gt<int>(FieldName, i16);
841 else if (Value is int i32)
842 return Builders<BsonDocument>.Filter.Gt<int>(FieldName, i32);
843 else if (Value is long i64)
844 return Builders<BsonDocument>.Filter.Gt<long>(FieldName, i64);
845 else if (Value is byte ui8)
846 return Builders<BsonDocument>.Filter.Gt<int>(FieldName, ui8);
847 else if (Value is ushort ui16)
848 return Builders<BsonDocument>.Filter.Gt<int>(FieldName, ui16);
849 else if (Value is uint ui32)
850 return Builders<BsonDocument>.Filter.Gt<long>(FieldName, ui32);
851 else if (Value is ulong ui64)
852 return Builders<BsonDocument>.Filter.Gt<Decimal128>(FieldName, ui64);
853 else if (Value is double d)
854 return Builders<BsonDocument>.Filter.Gt<double>(FieldName, d);
855 else if (Value is float f)
856 return Builders<BsonDocument>.Filter.Gt<double>(FieldName, f);
857 else if (Value is decimal d2)
858 return Builders<BsonDocument>.Filter.Gt<Decimal128>(FieldName, d2);
859 else if (Value is bool b)
860 return Builders<BsonDocument>.Filter.Gt<bool>(FieldName, b);
861 else if (Value is DateTime DT)
862 return Builders<BsonDocument>.Filter.Gt<long>(FieldName, (long)(DT - ObjectSerializer.UnixEpoch).TotalMilliseconds);
863 else if (Value is TimeSpan TS)
864 return Builders<BsonDocument>.Filter.Gt<string>(FieldName, TS.ToString());
865 else if (Value is Guid Guid)
866 return Builders<BsonDocument>.Filter.Gt<string>(FieldName, Guid.ToString());
867 else if (Value is ObjectId ObjectId)
868 return Builders<BsonDocument>.Filter.Gt<ObjectId>(FieldName, ObjectId);
869 else
870 throw UnhandledFilterValueDataType(Serializer.ValueType.FullName, FieldName, Value);
871 }
873 {
874 if (IsDefaultValue)
875 {
876 return Convert(new FilterOr(new FilterFieldGreaterThan(FieldName, Value),
877 new FilterFieldEqualTo(FieldName, Value)), Serializer);
878 }
879 else if (Value is string s)
880 {
881 if (HasType && FieldType == typeof(CaseInsensitiveString))
882 return Builders<BsonDocument>.Filter.Gte<string>(FieldName + "_L", s);
883 else
884 return Builders<BsonDocument>.Filter.Gte<string>(FieldName, s);
885 }
886 else if (Value is CaseInsensitiveString cis)
887 return Builders<BsonDocument>.Filter.Gte<string>(FieldName + "_L", cis.LowerCase);
888 else if (Value is sbyte i8)
889 return Builders<BsonDocument>.Filter.Gte<int>(FieldName, i8);
890 else if (Value is short i16)
891 return Builders<BsonDocument>.Filter.Gte<int>(FieldName, i16);
892 else if (Value is int i32)
893 return Builders<BsonDocument>.Filter.Gte<int>(FieldName, i32);
894 else if (Value is long i64)
895 return Builders<BsonDocument>.Filter.Gte<long>(FieldName, i64);
896 else if (Value is byte ui8)
897 return Builders<BsonDocument>.Filter.Gte<int>(FieldName, ui8);
898 else if (Value is ushort ui16)
899 return Builders<BsonDocument>.Filter.Gte<int>(FieldName, ui16);
900 else if (Value is uint ui32)
901 return Builders<BsonDocument>.Filter.Gte<long>(FieldName, ui32);
902 else if (Value is ulong ui64)
903 return Builders<BsonDocument>.Filter.Gte<Decimal128>(FieldName, ui64);
904 else if (Value is double d)
905 return Builders<BsonDocument>.Filter.Gte<double>(FieldName, d);
906 else if (Value is float f)
907 return Builders<BsonDocument>.Filter.Gte<double>(FieldName, f);
908 else if (Value is decimal d2)
909 return Builders<BsonDocument>.Filter.Gte<Decimal128>(FieldName, d2);
910 else if (Value is bool b)
911 return Builders<BsonDocument>.Filter.Gte<bool>(FieldName, b);
912 else if (Value is DateTime DT)
913 return Builders<BsonDocument>.Filter.Gte<long>(FieldName, (long)(DT - ObjectSerializer.UnixEpoch).TotalMilliseconds);
914 else if (Value is TimeSpan TS)
915 return Builders<BsonDocument>.Filter.Gte<string>(FieldName, TS.ToString());
916 else if (Value is Guid Guid)
917 return Builders<BsonDocument>.Filter.Gte<string>(FieldName, Guid.ToString());
918 else if (Value is ObjectId ObjectId)
919 return Builders<BsonDocument>.Filter.Gte<ObjectId>(FieldName, ObjectId);
920 else
921 throw UnhandledFilterValueDataType(Serializer.ValueType.FullName, FieldName, Value);
922 }
923 else if (Filter is FilterFieldLesserThan)
924 {
925 if (Value is string s)
926 {
927 if (HasType && FieldType == typeof(CaseInsensitiveString))
928 return Builders<BsonDocument>.Filter.Lt<string>(FieldName + "_L", s);
929 else
930 return Builders<BsonDocument>.Filter.Lt<string>(FieldName, s);
931 }
932 else if (Value is CaseInsensitiveString cis)
933 return Builders<BsonDocument>.Filter.Lt<string>(FieldName + "_L", cis.LowerCase);
934 else if (Value is sbyte i8)
935 return Builders<BsonDocument>.Filter.Lt<int>(FieldName, i8);
936 else if (Value is short i16)
937 return Builders<BsonDocument>.Filter.Lt<int>(FieldName, i16);
938 else if (Value is int i32)
939 return Builders<BsonDocument>.Filter.Lt<int>(FieldName, i32);
940 else if (Value is long i64)
941 return Builders<BsonDocument>.Filter.Lt<long>(FieldName, i64);
942 else if (Value is byte ui8)
943 return Builders<BsonDocument>.Filter.Lt<int>(FieldName, ui8);
944 else if (Value is ushort ui16)
945 return Builders<BsonDocument>.Filter.Lt<int>(FieldName, ui16);
946 else if (Value is uint ui32)
947 return Builders<BsonDocument>.Filter.Lt<long>(FieldName, ui32);
948 else if (Value is ulong ui64)
949 return Builders<BsonDocument>.Filter.Lt<Decimal128>(FieldName, ui64);
950 else if (Value is double d)
951 return Builders<BsonDocument>.Filter.Lt<double>(FieldName, d);
952 else if (Value is float f)
953 return Builders<BsonDocument>.Filter.Lt<double>(FieldName, f);
954 else if (Value is decimal d2)
955 return Builders<BsonDocument>.Filter.Lt<Decimal128>(FieldName, d2);
956 else if (Value is bool b)
957 return Builders<BsonDocument>.Filter.Lt<bool>(FieldName, b);
958 else if (Value is DateTime DT)
959 return Builders<BsonDocument>.Filter.Lt<long>(FieldName, (long)(DT - ObjectSerializer.UnixEpoch).TotalMilliseconds);
960 else if (Value is TimeSpan TS)
961 return Builders<BsonDocument>.Filter.Lt<string>(FieldName, TS.ToString());
962 else if (Value is Guid Guid)
963 return Builders<BsonDocument>.Filter.Lt<string>(FieldName, Guid.ToString());
964 else if (Value is ObjectId ObjectId)
965 return Builders<BsonDocument>.Filter.Lt<ObjectId>(FieldName, ObjectId);
966 else
967 throw UnhandledFilterValueDataType(Serializer.ValueType.FullName, FieldName, Value);
968 }
970 {
971 if (IsDefaultValue)
972 {
973 return Convert(new FilterOr(new FilterFieldLesserThan(FieldName, Value),
974 new FilterFieldEqualTo(FieldName, Value)), Serializer);
975 }
976 else if (Value is string s)
977 {
978 if (HasType && FieldType == typeof(CaseInsensitiveString))
979 return Builders<BsonDocument>.Filter.Lte<string>(FieldName + "_L", s);
980 else
981 return Builders<BsonDocument>.Filter.Lte<string>(FieldName, s);
982 }
983 else if (Value is CaseInsensitiveString cis)
984 return Builders<BsonDocument>.Filter.Lte<string>(FieldName + "_L", cis.LowerCase);
985 else if (Value is sbyte i8)
986 return Builders<BsonDocument>.Filter.Lte<int>(FieldName, i8);
987 else if (Value is short i16)
988 return Builders<BsonDocument>.Filter.Lte<int>(FieldName, i16);
989 else if (Value is int i32)
990 return Builders<BsonDocument>.Filter.Lte<int>(FieldName, i32);
991 else if (Value is long i64)
992 return Builders<BsonDocument>.Filter.Lte<long>(FieldName, i64);
993 else if (Value is byte ui8)
994 return Builders<BsonDocument>.Filter.Lte<int>(FieldName, ui8);
995 else if (Value is ushort ui16)
996 return Builders<BsonDocument>.Filter.Lte<int>(FieldName, ui16);
997 else if (Value is uint ui32)
998 return Builders<BsonDocument>.Filter.Lte<long>(FieldName, ui32);
999 else if (Value is ulong ui64)
1000 return Builders<BsonDocument>.Filter.Lte<Decimal128>(FieldName, ui64);
1001 else if (Value is double d)
1002 return Builders<BsonDocument>.Filter.Lte<double>(FieldName, d);
1003 else if (Value is float f)
1004 return Builders<BsonDocument>.Filter.Lte<double>(FieldName, f);
1005 else if (Value is decimal d2)
1006 return Builders<BsonDocument>.Filter.Lte<Decimal128>(FieldName, d2);
1007 else if (Value is bool b)
1008 return Builders<BsonDocument>.Filter.Lte<bool>(FieldName, b);
1009 else if (Value is DateTime DT)
1010 return Builders<BsonDocument>.Filter.Lte<long>(FieldName, (long)(DT - ObjectSerializer.UnixEpoch).TotalMilliseconds);
1011 else if (Value is TimeSpan TS)
1012 return Builders<BsonDocument>.Filter.Lte<string>(FieldName, TS.ToString());
1013 else if (Value is Guid Guid)
1014 return Builders<BsonDocument>.Filter.Lte<string>(FieldName, Guid.ToString());
1015 else if (Value is ObjectId ObjectId)
1016 return Builders<BsonDocument>.Filter.Lte<ObjectId>(FieldName, ObjectId);
1017 else
1018 throw UnhandledFilterValueDataType(Serializer.ValueType.FullName, FieldName, Value);
1019 }
1020 else
1021 throw UnknownFilterType(Filter);
1022 }
1023 else
1024 {
1026 {
1027 return Builders<BsonDocument>.Filter.Regex(Serializer.ToShortName(FilterFieldLikeRegEx.FieldName),
1029 }
1030 else
1031 throw UnknownFilterType(Filter);
1032 }
1033 }
1034
1035 private static NotSupportedException UnknownFilterType(Filter Filter)
1036 {
1037 return new NotSupportedException("Filters of type " + Filter.GetType().FullName + " not supported.");
1038 }
1039
1040 private static NotSupportedException UnhandledFilterValueDataType(string TypeName, string FieldName, object Value)
1041 {
1042 if (Value is null)
1043 {
1044 return new NotSupportedException("Null filter values for field " + TypeName + "." + FieldName +
1045 " not supported.");
1046 }
1047 else
1048 {
1049 return new NotSupportedException("Filter values of type " + Value.GetType().FullName +
1050 " for field " + TypeName + "." + FieldName + " not supported.");
1051 }
1052 }
1053
1062 public async Task<IPage<T>> FindFirst<T>(int PageSize, params string[] SortOrder)
1063 where T : class
1064 {
1065 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
1066 IEnumerable<T> Items = await this.Find<T>(0, PageSize, SortOrder);
1067 return new Page<T>(PageSize, null, null, SortOrder, Items, Serializer, this);
1068 }
1069
1079 public async Task<IPage<T>> FindFirst<T>(int PageSize, Filter Filter, params string[] SortOrder)
1080 where T : class
1081 {
1082 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
1083 IEnumerable<T> Items = await this.Find<T>(0, PageSize, Filter, SortOrder);
1084 return new Page<T>(PageSize, null, Filter, SortOrder, Items, Serializer, this);
1085 }
1086
1095 public async Task<IPage<object>> FindFirst(string Collection, int PageSize, params string[] SortOrder)
1096 {
1097 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(object));
1098 IEnumerable<object> Items = await this.Find(Collection, 0, PageSize, SortOrder);
1099 return new Page<object>(PageSize, Collection, null, SortOrder, Items, Serializer, this);
1100 }
1101
1111 public async Task<IPage<object>> FindFirst(string Collection, int PageSize, Filter Filter, params string[] SortOrder)
1112 {
1113 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(object));
1114 IEnumerable<object> Items = await this.Find(Collection, 0, PageSize, Filter, SortOrder);
1115 return new Page<object>(PageSize, Collection, Filter, SortOrder, Items, Serializer, this);
1116 }
1117
1128 public async Task<IPage<T>> FindFirst<T>(string Collection, int PageSize, Filter Filter, params string[] SortOrder)
1129 where T : class
1130 {
1131 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
1132 IEnumerable<T> Items = await this.Find<T>(Collection, 0, PageSize, Filter, SortOrder);
1133 return new Page<T>(PageSize, Collection, Filter, SortOrder, Items, Serializer, this);
1134 }
1135
1142 public Task<IPage<T>> FindNext<T>(IPage<T> Page)
1143 where T : class
1144 {
1145 if (Page is Page<T> CurrentPage)
1146 return CurrentPage.FindNext();
1147 else
1148 throw new IOException("Incompatible page.");
1149 }
1150
1156 public Task<IPage<object>> FindNext(IPage<object> Page)
1157 {
1158 if (Page is Page<object> CurrentPage)
1159 return CurrentPage.FindNext();
1160 else
1161 throw new IOException("Incompatible page.");
1162 }
1163
1170 public Task<T> TryLoadObject<T>(object ObjectId)
1171 where T : class
1172 {
1173 ObjectId OID;
1174
1175 if (ObjectId is ObjectId ObjId)
1176 OID = ObjId;
1177 else if (ObjectId is string s)
1178 OID = new ObjectId(s);
1179 else if (ObjectId is byte[] A)
1180 OID = new ObjectId(A);
1181 else if (ObjectId is Guid Guid)
1183 else
1184 throw new NotSupportedException("Unsupported type for Object ID: " + ObjectId.GetType().FullName);
1185
1186 return this.TryLoadObject<T>(OID);
1187 }
1188
1195 public async Task<T> TryLoadObject<T>(ObjectId ObjectId)
1196 where T : class
1197 {
1198 string Key = typeof(T).FullName + " " + ObjectId.ToString();
1199
1200 if (this.loadCache.TryGetValue(Key, out object Obj) && Obj is T Result)
1201 return Result;
1202
1203 ObjectSerializer S = this.GetObjectSerializerEx(typeof(T));
1204 IEnumerable<T> ReferencedObjects = await this.Find<T>(0, 2, new FilterFieldEqualTo(S.ObjectIdMemberName, ObjectId));
1205 T First = default;
1206
1207 foreach (T Item in ReferencedObjects)
1208 {
1209 if (First is null)
1210 First = Item;
1211 else
1212 throw new Exception("Multiple objects of type T found with object ID " + ObjectId.ToString());
1213 }
1214
1215 if (First is not null)
1216 this.loadCache.Add(Key, First); // Speeds up readout if reading multiple objects referencing a few common sub-objects.
1217
1218 return First;
1219 }
1220
1228 public Task<T> TryLoadObject<T>(string CollectionName, object ObjectId)
1229 where T : class
1230 {
1231 ObjectId OID;
1232
1233 if (ObjectId is ObjectId ObjId)
1234 OID = ObjId;
1235 else if (ObjectId is string s)
1236 OID = new ObjectId(s);
1237 else if (ObjectId is byte[] A)
1238 OID = new ObjectId(A);
1239 else if (ObjectId is Guid Guid)
1241 else
1242 throw new NotSupportedException("Unsupported type for Object ID: " + ObjectId.GetType().FullName);
1243
1244 return this.TryLoadObject<T>(CollectionName, OID);
1245 }
1246
1254 public async Task<T> TryLoadObject<T>(string CollectionName, ObjectId ObjectId)
1255 where T : class
1256 {
1257 string Key = typeof(T).FullName + " " + ObjectId.ToString();
1258
1259 if (this.loadCache.TryGetValue(Key, out object Obj) && Obj is T Result)
1260 return Result;
1261
1262 ObjectSerializer S = this.GetObjectSerializerEx(typeof(T));
1263 IEnumerable<T> ReferencedObjects = await this.Find<T>(CollectionName, 0, 2, new FilterFieldEqualTo(S.ObjectIdMemberName, ObjectId));
1264 T First = default;
1265
1266 foreach (T Item in ReferencedObjects)
1267 {
1268 if (First is null)
1269 First = Item;
1270 else
1271 throw new Exception("Multiple objects of type T found with object ID " + ObjectId.ToString());
1272 }
1273
1274 if (First is not null)
1275 this.loadCache.Add(Key, First); // Speeds up readout if reading multiple objects referencing a few common sub-objects.
1276
1277 return First;
1278 }
1279
1286 public async Task<object> TryLoadObject(string CollectionName, object ObjectId)
1287 {
1288 ObjectId OID;
1289
1290 if (ObjectId is ObjectId ObjId)
1291 OID = ObjId;
1292 else if (ObjectId is string s)
1293 OID = new ObjectId(s);
1294 else if (ObjectId is byte[] A)
1295 OID = new ObjectId(A);
1296 else if (ObjectId is Guid Guid)
1298 else
1299 throw new NotSupportedException("Unsupported type for Object ID: " + ObjectId.GetType().FullName);
1300
1301 ObjectSerializer S = this.GetObjectSerializerEx(typeof(object));
1302 IEnumerable<object> ReferencedObjects = await this.Find(CollectionName, 0, 2, new FilterFieldEqualTo(S.ObjectIdMemberName, OID));
1303 object First = null;
1304
1305 foreach (object Item in ReferencedObjects)
1306 {
1307 if (First is null)
1308 First = Item;
1309 else
1310 throw new Exception("Multiple objects of type T found with object ID " + ObjectId.ToString());
1311 }
1312
1313 return First;
1314 }
1315
1316 private readonly Cache<string, object> loadCache = new(10000, new TimeSpan(0, 0, 10), new TimeSpan(0, 0, 5), true); // TODO: Make parameters configurable.
1317
1329 public Task<bool> Process<T>(IProcessor<T> Processor, int Offset, int MaxCount, params string[] SortOrder)
1330 where T : class
1331 {
1332 return this.Process<T>(Processor, Offset, MaxCount, (Filter)null, SortOrder);
1333 }
1334
1347 public Task<bool> Process<T>(IProcessor<T> Processor, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
1348 where T : class
1349 {
1350 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
1351 string CollectionName = Serializer.CollectionName(null);
1352 IMongoCollection<BsonDocument> Collection;
1353 FilterDefinition<BsonDocument> BsonFilter;
1354
1355 if (string.IsNullOrEmpty(CollectionName))
1356 Collection = this.defaultCollection;
1357 else
1358 Collection = this.GetCollection(CollectionName);
1359
1360 if (Filter is null)
1361 BsonFilter = new BsonDocument();
1362 else
1363 BsonFilter = Convert(Filter, Serializer);
1364
1365 return Process(Processor, Serializer, Collection, Offset, MaxCount, BsonFilter, null, SortOrder);
1366 }
1367
1381 public Task<bool> Process<T>(IProcessor<T> Processor, int Offset, int MaxCount, Filter Filter,
1382 T ContinueAfter, params string[] SortOrder)
1383 where T : class
1384 {
1385 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
1386 string CollectionName = Serializer.CollectionName(null);
1387 IMongoCollection<BsonDocument> Collection;
1388 FilterDefinition<BsonDocument> BsonFilter;
1389
1390 if (string.IsNullOrEmpty(CollectionName))
1391 Collection = this.defaultCollection;
1392 else
1393 Collection = this.GetCollection(CollectionName);
1394
1395 if (Filter is null)
1396 BsonFilter = new BsonDocument();
1397 else
1398 BsonFilter = Convert(Filter, Serializer);
1399
1400 return Process(Processor, Serializer, Collection, Offset, MaxCount, BsonFilter, ContinueAfter, SortOrder);
1401 }
1402
1415 public Task<bool> Process<T>(IProcessor<T> Processor, int Offset, int MaxCount, FilterDefinition<BsonDocument> BsonFilter,
1416 params string[] SortOrder)
1417 where T : class
1418 {
1419 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
1420 string CollectionName = Serializer.CollectionName(null);
1421 IMongoCollection<BsonDocument> Collection;
1422
1423 if (string.IsNullOrEmpty(CollectionName))
1424 Collection = this.defaultCollection;
1425 else
1426 Collection = this.GetCollection(CollectionName);
1427
1428 return Process(Processor, Serializer, Collection, Offset, MaxCount, BsonFilter, null, SortOrder);
1429 }
1430
1443 public Task<bool> Process<T>(IProcessor<T> Processor, string CollectionName, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
1444 where T : class
1445 {
1446 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
1447 IMongoCollection<BsonDocument> Collection;
1448 FilterDefinition<BsonDocument> BsonFilter;
1449
1450 if (string.IsNullOrEmpty(CollectionName))
1451 Collection = this.defaultCollection;
1452 else
1453 Collection = this.GetCollection(CollectionName);
1454
1455 if (Filter is null)
1456 BsonFilter = new BsonDocument();
1457 else
1458 BsonFilter = Convert(Filter, Serializer);
1459
1460 return Process(Processor, Serializer, Collection, Offset, MaxCount, BsonFilter, null, SortOrder);
1461 }
1462
1476 public Task<bool> Process<T>(IProcessor<T> Processor, string CollectionName, int Offset, int MaxCount, Filter Filter,
1477 T ContinueAfter, params string[] SortOrder)
1478 where T : class
1479 {
1480 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
1481 IMongoCollection<BsonDocument> Collection;
1482 FilterDefinition<BsonDocument> BsonFilter;
1483
1484 if (string.IsNullOrEmpty(CollectionName))
1485 Collection = this.defaultCollection;
1486 else
1487 Collection = this.GetCollection(CollectionName);
1488
1489 if (Filter is null)
1490 BsonFilter = new BsonDocument();
1491 else
1492 BsonFilter = Convert(Filter, Serializer);
1493
1494 return Process(Processor, Serializer, Collection, Offset, MaxCount, BsonFilter, ContinueAfter, SortOrder);
1495 }
1496
1508 public Task<bool> Process(IProcessor<object> Processor, string CollectionName, int Offset, int MaxCount, params string[] SortOrder)
1509 {
1510 return this.Process(Processor, CollectionName, Offset, MaxCount, null, SortOrder);
1511 }
1512
1525 public Task<bool> Process(IProcessor<object> Processor, string CollectionName, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
1526 {
1527 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(object));
1528 IMongoCollection<BsonDocument> Collection;
1529 FilterDefinition<BsonDocument> BsonFilter;
1530
1531 if (string.IsNullOrEmpty(CollectionName))
1532 Collection = this.defaultCollection;
1533 else
1534 Collection = this.GetCollection(CollectionName);
1535
1536 if (Filter is null)
1537 BsonFilter = new BsonDocument();
1538 else
1539 BsonFilter = Convert(Filter, Serializer);
1540
1541 return Process(Processor, Serializer, Collection, Offset, MaxCount, BsonFilter, SortOrder);
1542 }
1543
1544 private static async Task<bool> Process<T>(IProcessor<T> Processor, ObjectSerializer Serializer, IMongoCollection<BsonDocument> Collection,
1545 int Offset, int MaxCount, FilterDefinition<BsonDocument> BsonFilter, T ContinueAfter, params string[] SortOrder)
1546 where T : class
1547 {
1548 if (ContinueAfter is not null)
1549 throw new NotImplementedException("Paginated searches not implemented in MongoDB provider.");
1550
1551 IFindFluent<BsonDocument, BsonDocument> ResultSet = Collection.Find(BsonFilter);
1552
1553 if (SortOrder.Length > 0)
1554 {
1555 SortDefinition<BsonDocument> SortDefinition = null;
1556
1557 foreach (string SortBy in SortOrder)
1558 {
1559 if (SortDefinition is null)
1560 {
1561 if (SortBy.StartsWith('-'))
1562 SortDefinition = Builders<BsonDocument>.Sort.Descending(Serializer.ToShortName(SortBy[1..]));
1563 else
1564 SortDefinition = Builders<BsonDocument>.Sort.Ascending(Serializer.ToShortName(SortBy));
1565 }
1566 else
1567 {
1568 if (SortBy.StartsWith('-'))
1569 SortDefinition = SortDefinition.Descending(Serializer.ToShortName(SortBy[1..]));
1570 else
1571 SortDefinition = SortDefinition.Ascending(Serializer.ToShortName(SortBy));
1572 }
1573 }
1574
1575 ResultSet = ResultSet.Sort(SortDefinition);
1576 }
1577
1578 if (Offset > 0)
1579 ResultSet = ResultSet.Skip(Offset);
1580
1581 if (MaxCount < int.MaxValue)
1582 ResultSet = ResultSet.Limit(MaxCount);
1583
1584 IAsyncCursor<BsonDocument> Cursor = await ResultSet.ToCursorAsync();
1585 BsonDeserializationArgs Args = new()
1586 {
1587 NominalType = typeof(T)
1588 };
1589
1590 bool Asynchronous = Processor.IsAsynchronous;
1591 bool Continue;
1592
1593 while (await Cursor.MoveNextAsync())
1594 {
1595 foreach (BsonDocument Document in Cursor.Current)
1596 {
1597 BsonDocumentReader Reader = new(Document);
1598 BsonDeserializationContext Context = BsonDeserializationContext.CreateRoot(Reader);
1599
1600 if (Serializer.Deserialize(Context, Args) is T Obj)
1601 {
1602 if (Asynchronous)
1603 Continue = await Processor.ProcessAsync(Obj);
1604 else
1605 Continue = Processor.Process(Obj);
1606
1607 if (!Continue)
1608 return false;
1609 }
1610 }
1611 }
1612
1613 if (Asynchronous)
1614 return await Processor.FlushAsync();
1615 else
1616 return Processor.Flush();
1617 }
1618
1623 public async Task Update(object Object)
1624 {
1625 ObjectSerializer Serializer = this.GetObjectSerializerEx(Object);
1626 ObjectId ObjectId = await Serializer.GetObjectId(Object, false);
1627 string CollectionName = Serializer.CollectionName(Object);
1628 IMongoCollection<BsonDocument> Collection;
1629
1630 if (string.IsNullOrEmpty(CollectionName))
1631 Collection = this.defaultCollection;
1632 else
1633 Collection = this.GetCollection(CollectionName);
1634
1635 BsonDocument Doc = Object.ToBsonDocument(Object.GetType(), Serializer);
1636 await Collection.ReplaceOneAsync(Builders<BsonDocument>.Filter.Eq<ObjectId>("_id", ObjectId), Doc);
1637 }
1638
1643 public Task Update(params object[] Objects)
1644 {
1645 return this.Update((IEnumerable<object>)Objects);
1646 }
1647
1652 public async Task Update(IEnumerable<object> Objects)
1653 {
1654 foreach (object Obj in Objects)
1655 await this.Update(Obj);
1656 }
1657
1663 public Task UpdateLazy(object Object, ObjectCallback Callback)
1664 => Process(Object, this.Update(Object), Callback);
1665
1671 public Task UpdateLazy(object[] Objects, ObjectsCallback Callback)
1672 => Process(Objects, this.Update(Objects), Callback);
1673
1679 public Task UpdateLazy(IEnumerable<object> Objects, ObjectsCallback Callback)
1680 => Process(Objects, this.Update(Objects), Callback);
1681
1686 public async Task Delete(object Object)
1687 {
1688 ObjectSerializer Serializer = this.GetObjectSerializerEx(Object);
1689 ObjectId ObjectId = await Serializer.GetObjectId(Object, false);
1690 string CollectionName = Serializer.CollectionName(Object);
1691 IMongoCollection<BsonDocument> Collection;
1692
1693 if (string.IsNullOrEmpty(CollectionName))
1694 Collection = this.defaultCollection;
1695 else
1696 Collection = this.GetCollection(CollectionName);
1697
1698 await Collection.DeleteOneAsync(Builders<BsonDocument>.Filter.Eq<ObjectId>("_id", ObjectId));
1699 }
1700
1705 public Task Delete(params object[] Objects)
1706 {
1707 return this.Delete((IEnumerable<object>)Objects);
1708 }
1709
1714 public async Task Delete(IEnumerable<object> Objects)
1715 {
1716 foreach (object Obj in Objects)
1717 await this.Delete(Obj);
1718 }
1719
1720 private static async Task Process(object Object, Task Op, ObjectCallback Callback)
1721 {
1722 await Op;
1723 if (Callback is not null)
1724 Callback(Object);
1725 }
1726
1727 private static async Task Process(IEnumerable<object> Objects, Task Op, ObjectsCallback Callback)
1728 {
1729 await Op;
1730
1731 if (Callback is not null)
1732 Callback(Objects);
1733 }
1734
1740 public Task DeleteLazy(object Object, ObjectCallback Callback)
1741 => Process(Object, this.Delete(Object), Callback);
1742
1748 public Task DeleteLazy(object[] Objects, ObjectsCallback Callback)
1749 => Process(Objects, this.Delete(Objects), Callback);
1750
1756 public Task DeleteLazy(IEnumerable<object> Objects, ObjectsCallback Callback)
1757 => Process(Objects, this.Delete(Objects), Callback);
1758
1768 public async Task<IEnumerable<T>> FindDelete<T>(int Offset, int MaxCount, params string[] SortOrder)
1769 where T : class
1770 {
1771 IEnumerable<T> Result = await this.Find<T>(Offset, MaxCount, SortOrder);
1772 await this.Delete(Result);
1773 return Result;
1774 }
1775
1786 public async Task<IEnumerable<T>> FindDelete<T>(int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
1787 where T : class
1788 {
1789 IEnumerable<T> Result = await this.Find<T>(Offset, MaxCount, Filter, SortOrder);
1790 await this.Delete(Result);
1791 return Result;
1792 }
1793
1803 public async Task<IEnumerable<object>> FindDelete(string Collection, int Offset, int MaxCount, params string[] SortOrder)
1804 {
1805 IEnumerable<object> Result = await this.Find(Collection, Offset, MaxCount, SortOrder);
1806 await this.Delete(Result);
1807 return Result;
1808 }
1809
1820 public async Task<IEnumerable<object>> FindDelete(string Collection, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
1821 {
1822 IEnumerable<object> Result = await this.Find(Collection, Offset, MaxCount, Filter, SortOrder);
1823 await this.Delete(Result);
1824 return Result;
1825 }
1826
1836 public async Task DeleteLazy<T>(int Offset, int MaxCount, string[] SortOrder, ObjectsCallback Callback)
1837 where T : class
1838 {
1839 IEnumerable<T> Objects = await this.FindDelete<T>(Offset, MaxCount, SortOrder);
1840 if (Callback is not null)
1841 Callback(Objects);
1842 }
1843
1854 public async Task DeleteLazy<T>(int Offset, int MaxCount, Filter Filter, string[] SortOrder, ObjectsCallback Callback)
1855 where T : class
1856 {
1857 IEnumerable<T> Objects = await this.FindDelete<T>(Offset, MaxCount, Filter, SortOrder);
1858 if (Callback is not null)
1859 Callback(Objects);
1860 }
1861
1871 public async Task DeleteLazy(string Collection, int Offset, int MaxCount, string[] SortOrder, ObjectsCallback Callback)
1872 {
1873 IEnumerable<object> Objects = await this.FindDelete(Collection, Offset, MaxCount, SortOrder);
1874 if (Callback is not null)
1875 Callback(Objects);
1876 }
1877
1888 public async Task DeleteLazy(string Collection, int Offset, int MaxCount, Filter Filter, string[] SortOrder, ObjectsCallback Callback)
1889 {
1890 IEnumerable<object> Objects = await this.FindDelete(Collection, Offset, MaxCount, Filter, SortOrder);
1891 if (Callback is not null)
1892 Callback(Objects);
1893 }
1894
1900 public Task Clear(string CollectionName)
1901 {
1902 IMongoCollection<BsonDocument> Collection = this.GetCollection(CollectionName);
1903 return Collection.DeleteManyAsync(FilterDefinition<BsonDocument>.Empty);
1904 }
1905
1912 public async Task AddIndex(string CollectionName, string[] FieldNames)
1913 {
1914 IMongoCollection<BsonDocument> Collection;
1915 List<BsonDocument> Indices;
1916
1917 if (string.IsNullOrEmpty(CollectionName))
1918 Collection = this.DefaultCollection;
1919 else
1920 Collection = this.GetCollection(CollectionName);
1921
1922 IAsyncCursor<BsonDocument> Cursor = await Collection.Indexes.ListAsync();
1923 Indices = await Cursor.ToListAsync<BsonDocument>();
1924
1925 await ObjectSerializer.CheckIndexExists(Collection, Indices, FieldNames, null);
1926 }
1927
1934 public async Task RemoveIndex(string CollectionName, string[] FieldNames)
1935 {
1936 IMongoCollection<BsonDocument> Collection;
1937 List<BsonDocument> Indices;
1938
1939 if (string.IsNullOrEmpty(CollectionName))
1940 Collection = this.DefaultCollection;
1941 else
1942 Collection = this.GetCollection(CollectionName);
1943
1944 IAsyncCursor<BsonDocument> Cursor = await Collection.Indexes.ListAsync();
1945 Indices = await Cursor.ToListAsync<BsonDocument>();
1946
1947 await ObjectSerializer.RemoveIndex(Collection, Indices, FieldNames);
1948 }
1949
1957 public async Task<string[][]> GetIndices(string CollectionName)
1958 {
1959 IMongoCollection<BsonDocument> Collection;
1960
1961 if (string.IsNullOrEmpty(CollectionName))
1962 Collection = this.DefaultCollection;
1963 else
1964 Collection = this.GetCollection(CollectionName);
1965
1966 IAsyncCursor<BsonDocument> Cursor = await Collection.Indexes.ListAsync();
1967 ChunkedList<string[]> Result = [];
1968
1969 while (await Cursor.MoveNextAsync())
1970 {
1971 foreach (BsonDocument Index in Cursor.Current)
1972 {
1973 ChunkedList<string> FieldNames = null;
1974
1975 foreach (BsonElement E in Index.Elements)
1976 {
1977 if (E.Name != "key")
1978 continue;
1979
1980 FieldNames = [];
1981
1982 foreach (BsonElement E2 in E.Value.AsBsonDocument.Elements)
1983 {
1984 // Value is typically 1 (ascending) or -1 (descending). Can also be "text" etc.
1985 if (E2.Value.IsInt32 || E2.Value.IsInt64 || E2.Value.IsDouble)
1986 {
1987 double v = E2.Value.ToDouble();
1988 if (v < 0)
1989 FieldNames.Add("-" + E2.Name);
1990 else
1991 FieldNames.Add(E2.Name);
1992 }
1993 else if (E2.Value.IsString)
1994 {
1995 // For text or hashed indexes we just report the field name without sign.
1996 FieldNames.Add(E2.Name);
1997 }
1998 else
1999 {
2000 // Fallback: just add field name.
2001 FieldNames.Add(E2.Name);
2002 }
2003 }
2004
2005 break; // Done with this index.
2006 }
2007
2008 if (FieldNames is not null && FieldNames.Count > 0)
2009 Result.Add([.. FieldNames]);
2010 }
2011 }
2012
2013 return [.. Result];
2014 }
2015
2023 public Task<string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData)
2024 {
2025 return this.Analyze(Output, XsltPath, ProgramDataFolder, ExportData, false);
2026 }
2027
2036 public Task<string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData, ProfilerThread Thread)
2037 {
2038 return this.Analyze(Output, XsltPath, ProgramDataFolder, ExportData, false, Thread);
2039 }
2040
2048 public Task<string[]> Repair(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData)
2049 {
2050 return this.Analyze(Output, XsltPath, ProgramDataFolder, ExportData, true);
2051 }
2052
2061 public Task<string[]> Repair(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData, ProfilerThread Thread)
2062 {
2063 return this.Analyze(Output, XsltPath, ProgramDataFolder, ExportData, true, Thread);
2064 }
2065
2074 public Task<string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData, bool Repair)
2075 {
2076 return this.Analyze(null, XsltPath, ProgramDataFolder, ExportData, Repair, null);
2077 }
2078
2088 public async Task<string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData, bool Repair,
2089 ProfilerThread Thread)
2090 {
2091 Thread?.Start();
2092 Output.WriteStartDocument();
2093
2094 if (!string.IsNullOrEmpty(XsltPath))
2095 {
2096 if (File.Exists(XsltPath))
2097 {
2098 try
2099 {
2100 byte[] XsltBin = File.ReadAllBytes(XsltPath);
2101
2102 Output.WriteProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"data:text/xsl;base64," +
2103 System.Convert.ToBase64String(XsltBin) + "\"");
2104 }
2105 catch (Exception)
2106 {
2107 Output.WriteProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"" + Encode(XsltPath) + "\"");
2108 }
2109 }
2110 else
2111 Output.WriteProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"" + Encode(XsltPath) + "\"");
2112 }
2113
2114 Output.WriteStartElement("DatabaseStatistics", "http://waher.se/Schema/Persistence/Statistics.xsd");
2115
2116 foreach (string CollectionName in (await this.database.ListCollectionNamesAsync()).ToEnumerable())
2117 {
2118 Thread?.NewState(CollectionName);
2119
2120 IMongoCollection<BsonDocument> Collection = this.database.GetCollection<BsonDocument>(CollectionName);
2121
2122 Output.WriteStartElement("File");
2123 Output.WriteAttributeString("id", Collection.CollectionNamespace.FullName);
2124 Output.WriteAttributeString("collectionName", CollectionName);
2125 Output.WriteAttributeString("count", (await Collection.CountDocumentsAsync(Builders<BsonDocument>.Filter.Empty)).ToString());
2126
2127 if (Collection.Settings.WriteEncoding is not null)
2128 Output.WriteAttributeString("encoding", Collection.Settings.WriteEncoding.WebName);
2129
2130 if (Collection.Settings.WriteConcern.WTimeout.HasValue)
2131 Output.WriteAttributeString("timeoutMs", ((int)Collection.Settings.WriteConcern.WTimeout.Value.TotalMilliseconds).ToString());
2132
2133 foreach (BsonDocument Index in (await Collection.Indexes.ListAsync()).ToEnumerable())
2134 {
2135 List<string> FieldNames = [];
2136
2137 Output.WriteStartElement("Index");
2138
2139 foreach (BsonElement E in Index.Elements)
2140 {
2141 switch (E.Name)
2142 {
2143 case "key":
2144 foreach (BsonElement E2 in E.Value.AsBsonDocument.Elements)
2145 {
2146 if (E2.Value.AsInt32 < 0)
2147 FieldNames.Add("-" + E2.Name);
2148 else
2149 FieldNames.Add(E2.Name);
2150 }
2151 break;
2152
2153 case "name":
2154 Output.WriteAttributeString("id", E.Value.AsString);
2155 break;
2156 }
2157 }
2158
2159 foreach (string Field in FieldNames)
2160 Output.WriteElementString("Field", Field);
2161
2162 Output.WriteEndElement();
2163 }
2164
2165 Output.WriteEndElement();
2166 }
2167
2168 Output.WriteEndElement();
2169 Output.WriteEndDocument();
2170
2171 Thread?.Idle();
2172 Thread?.Stop();
2173
2174 return [];
2175 }
2176
2182 public Task<string[]> Repair(params string[] CollectionNames)
2183 {
2184 return Task.FromResult<string[]>([]);
2185 }
2186
2193 public Task<string[]> Repair(ProfilerThread Thread, params string[] CollectionNames)
2194 {
2195 return Task.FromResult<string[]>([]);
2196 }
2197
2198 private static string Encode(string s)
2199 {
2200 return s.
2201 Replace("&", "&amp;").
2202 Replace("<", "&lt;").
2203 Replace(">", "&gt;").
2204 Replace("\"", "&quot;").
2205 Replace("'", "&apos;");
2206 }
2207
2214 public Task<bool> Export(IDatabaseExport Output, string[] CollectionNames)
2215 {
2216 return this.Export(Output, CollectionNames, null);
2217 }
2218
2226 public async Task<bool> Export(IDatabaseExport Output, string[] CollectionNames, ProfilerThread Thread)
2227 {
2228 bool Continue;
2229
2230 Thread?.Start();
2231 if (!await Output.StartDatabase(this))
2232 return false;
2233 try
2234 {
2236 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(GenericObject));
2237 BsonDeserializationArgs Args = new()
2238 {
2239 NominalType = typeof(GenericObject)
2240 };
2241
2242 foreach (string CollectionName in (await this.database.ListCollectionNamesAsync()).ToEnumerable())
2243 {
2244 if (CollectionNames is not null && Array.IndexOf(CollectionNames, CollectionName) < 0)
2245 continue;
2246
2247 if (Filter is not null && !Filter.CanExportCollection(CollectionName))
2248 continue;
2249
2250 Thread?.NewState(CollectionName);
2251
2252 IMongoCollection<BsonDocument> Collection = this.database.GetCollection<BsonDocument>(CollectionName);
2253
2254 if (!await Output.StartCollection(CollectionName))
2255 return false;
2256 try
2257 {
2258 foreach (BsonDocument Index in (await Collection.Indexes.ListAsync()).ToEnumerable())
2259 {
2260 if (!await Output.StartIndex())
2261 return false;
2262
2263 foreach (BsonElement E in Index.Elements)
2264 {
2265 if (E.Name == "key")
2266 {
2267 foreach (BsonElement E2 in E.Value.AsBsonDocument.Elements)
2268 {
2269 if (!await Output.ReportIndexField(E2.Name, E2.Value.AsInt32 > 0))
2270 return false;
2271 }
2272
2273 break;
2274 }
2275 }
2276
2277 if (!await Output.EndIndex())
2278 return false;
2279 }
2280
2281 foreach (BsonDocument Doc in (await Collection.FindAsync<BsonDocument>(Builders<BsonDocument>.Filter.Empty)).ToEnumerable())
2282 {
2283 BsonDocumentReader Reader = new(Doc);
2284 BsonDeserializationContext Context = BsonDeserializationContext.CreateRoot(Reader);
2285
2286 object Object = Serializer.Deserialize(Context, Args);
2287
2288 if (Object is GenericObject Obj)
2289 {
2290 if (Filter is not null && !Filter.CanExportObject(Obj))
2291 continue;
2292
2293 if (await Output.StartObject(Obj.ObjectId.ToString(), Obj.TypeName) is null)
2294 return false;
2295 try
2296 {
2297 foreach (KeyValuePair<string, object> P in Obj)
2298 {
2299 if (P.Value is ObjectId ObjectId)
2300 {
2301 if (!await Output.ReportProperty(P.Key, GeneratedObjectSerializerBase.ObjectIdToGuid(ObjectId)))
2302 return false;
2303 }
2304 else
2305 {
2306 if (!await Output.ReportProperty(P.Key, P.Value))
2307 return false;
2308 }
2309 }
2310 }
2311 catch (Exception ex)
2312 {
2313 Thread?.Exception(ex);
2314 if (!await ReportException(ex, Output))
2315 return false;
2316 }
2317 finally
2318 {
2319 Continue = await Output.EndObject();
2320 }
2321
2322 if (!Continue)
2323 return false;
2324 }
2325 else if (Object is not null)
2326 {
2327 if (!await Output.ReportError("Unable to load object " + Doc["_id"].AsString + "."))
2328 return false;
2329 }
2330 }
2331 }
2332 catch (Exception ex)
2333 {
2334 Thread?.Exception(ex);
2335 if (!await ReportException(ex, Output))
2336 return false;
2337 }
2338 finally
2339 {
2340 Continue = await Output.EndCollection();
2341 }
2342
2343 if (!Continue)
2344 return false;
2345 }
2346 }
2347 catch (Exception ex)
2348 {
2349 Thread?.Exception(ex);
2350 if (!await ReportException(ex, Output))
2351 return false;
2352 }
2353 finally
2354 {
2355 Continue = await Output.EndDatabase();
2356 Thread?.Idle();
2357 Thread?.Stop();
2358 }
2359
2360 return Continue;
2361 }
2362
2363 private static async Task<bool> ReportException(Exception ex, IDatabaseExport Output)
2364 {
2365 ex = Log.UnnestException(ex);
2366
2367 if (ex is AggregateException ex2)
2368 {
2369 foreach (Exception ex3 in ex2.InnerExceptions)
2370 {
2371 if (!await Output.ReportException(ex3))
2372 return false;
2373 }
2374
2375 return true;
2376 }
2377 else
2378 return await Output.ReportException(ex);
2379 }
2380
2388 public Task Iterate<T>(IDatabaseIteration<T> Recipient, string[] CollectionNames)
2389 where T : class
2390 {
2391 return this.Iterate(Recipient, CollectionNames, null);
2392 }
2393
2402 public async Task Iterate<T>(IDatabaseIteration<T> Recipient, string[] CollectionNames, ProfilerThread Thread)
2403 where T : class
2404 {
2405 Thread?.Start();
2406 await Recipient.StartDatabase();
2407 try
2408 {
2409 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(T));
2410 BsonDeserializationArgs Args = new()
2411 {
2412 NominalType = typeof(GenericObject)
2413 };
2414
2415 foreach (string CollectionName in (await this.database.ListCollectionNamesAsync()).ToEnumerable())
2416 {
2417 if (CollectionNames is not null && Array.IndexOf(CollectionNames, CollectionName) < 0)
2418 continue;
2419
2420 Thread?.NewState(CollectionName);
2421
2422 IMongoCollection<BsonDocument> Collection = this.database.GetCollection<BsonDocument>(CollectionName);
2423
2424 await Recipient.StartCollection(CollectionName);
2425 try
2426 {
2427 foreach (BsonDocument Doc in (await Collection.FindAsync<BsonDocument>(Builders<BsonDocument>.Filter.Empty)).ToEnumerable())
2428 {
2429 BsonDocumentReader Reader = new(Doc);
2430 BsonDeserializationContext Context = BsonDeserializationContext.CreateRoot(Reader);
2431
2432 object Object = Serializer.Deserialize(Context, Args);
2433
2434 if (Object is T Obj)
2435 await Recipient.ProcessObject(Obj);
2436 else if (Object is not null)
2437 {
2438 ObjectId ObjectId = await Serializer.GetObjectId(Object, false);
2439 if (ObjectId != ObjectId.Empty)
2440 await Recipient.IncompatibleObject(ObjectId);
2441 }
2442 }
2443 }
2444 catch (Exception ex)
2445 {
2446 Thread?.Exception(ex);
2447 ReportException(ex, Recipient);
2448 }
2449 finally
2450 {
2451 await Recipient.EndCollection();
2452 }
2453 }
2454 }
2455 catch (Exception ex)
2456 {
2457 Thread?.Exception(ex);
2458 ReportException(ex, Recipient);
2459 }
2460 finally
2461 {
2462 await Recipient.EndDatabase();
2463 Thread?.Idle();
2464 Thread?.Stop();
2465 }
2466 }
2467
2468 private static void ReportException<T>(Exception ex, IDatabaseIteration<T> Recipient)
2469 where T : class
2470 {
2471 ex = Events.Log.UnnestException(ex);
2472
2473 if (ex is AggregateException ex2)
2474 {
2475 foreach (Exception ex3 in ex2.InnerExceptions)
2476 Recipient.ReportException(ex3);
2477 }
2478 else
2479 Recipient.ReportException(ex);
2480 }
2481
2485 public Task StartBulk()
2486 {
2487 return Task.CompletedTask;
2488 }
2489
2493 public Task EndBulk()
2494 {
2495 return Task.CompletedTask;
2496 }
2497
2501 public Task Start()
2502 {
2503 return Task.CompletedTask;
2504 }
2505
2509 public Task Stop()
2510 {
2511 return Task.CompletedTask;
2512 }
2513
2517 public Task Flush()
2518 {
2519 return Task.CompletedTask;
2520 }
2521
2527 public Task<IPersistentDictionary> GetDictionary(string Collection)
2528 {
2529 return Task.FromResult<IPersistentDictionary>(new StringDictionary("DICT_" + Collection, this)); // TODO
2530 }
2531
2536 public async Task<string[]> GetDictionaries()
2537 {
2538 List<string> Collections = [];
2539
2540 foreach (string CollectionName in (await this.database.ListCollectionNamesAsync()).ToEnumerable())
2541 {
2542 if (CollectionName.StartsWith("DICT_"))
2543 Collections.Add(CollectionName);
2544 }
2545
2546 return [.. Collections];
2547 }
2548
2555 public Task<IPersistedQueue> GetQueue(string QueueName, bool CanBeNull)
2556 {
2558 ?? throw new NotSupportedException("No queue collection creator found for database provider.");
2559
2560 return Collection.GetQueue(this, QueueName, CanBeNull);
2561 }
2562
2567 public async Task<string[]> GetQueues()
2568 {
2569 IMongoCollection<BsonDocument> Collection = this.GetCollection(QueuedItem.QueuedItemCollectionName);
2570 FilterDefinition<BsonDocument> BsonFilter = Builders<BsonDocument>.Filter.Ne<string>("QueueName", null);
2571
2572 using IAsyncCursor<string> Cursor = await Collection.DistinctAsync<string>("QueueName", BsonFilter);
2573
2574 return [.. await Cursor.ToListAsync()];
2575 }
2576
2581 public async Task<string[]> GetCollections()
2582 {
2583 List<string> Collections = [];
2584
2585 foreach (string CollectionName in (await this.database.ListCollectionNamesAsync()).ToEnumerable())
2586 {
2587 if (!CollectionName.StartsWith("DICT_"))
2588 Collections.Add(CollectionName);
2589 }
2590
2591 return [.. Collections];
2592 }
2593
2599 public Task<string> GetCollection(Type Type)
2600 {
2601 ObjectSerializer Serializer = this.GetObjectSerializerEx(Type);
2602 return Task.FromResult(Serializer.CollectionName(null));
2603 }
2604
2610 public Task<string> GetCollection(object Object)
2611 {
2612 ObjectSerializer Serializer = this.GetObjectSerializerEx(Object);
2613 return Task.FromResult(Serializer.CollectionName(Object));
2614 }
2615
2623 public async Task<bool> IsLabel(string CollectionName, string Label)
2624 {
2625 IMongoCollection<BsonDocument> Collection = this.GetCollection(CollectionName);
2626 FilterDefinition<BsonDocument> BsonFilter = Builders<BsonDocument>.Filter.Ne<string>(Label, null);
2627 IFindFluent<BsonDocument, BsonDocument> ResultSet = Collection.Find<BsonDocument>(BsonFilter);
2628
2629 return await ResultSet.SingleAsync<BsonDocument>() is not null;
2630 }
2631
2636 public Task<string[]> GetLabels(string Collection)
2637 {
2638 throw new NotImplementedException();
2639 }
2640
2646 public async Task<object> TryGetObjectId(object Object)
2647 {
2648 if (Object is null)
2649 return null;
2650
2651 IObjectSerializer Serializer = this.GetObjectSerializer(Object.GetType());
2652 if (Serializer is ObjectSerializer SerializerEx &&
2653 SerializerEx.HasObjectId(Object))
2654 {
2655 return await SerializerEx.GetObjectId(Object, false);
2656 }
2657 else
2658 return null;
2659 }
2660
2665 public Task DropCollection(string CollectionName)
2666 {
2667 lock (this.collections)
2668 {
2669 this.collections.Remove(CollectionName);
2670
2671 if (CollectionName == this.lastCollectionName)
2672 {
2673 this.lastCollection = null;
2674 this.lastCollectionName = string.Empty;
2675 }
2676 }
2677
2678 return this.database.DropCollectionAsync(CollectionName);
2679 }
2680
2686 public Task<GenericObject> Generalize(object Object)
2687 {
2688 if (Object is null)
2689 return Task.FromResult<GenericObject>(null);
2690
2691 ObjectSerializer Serializer = this.GetObjectSerializerEx(Object);
2692 string CollectionName = Serializer.CollectionName(Object);
2693
2694 BsonDocument Doc = Object.ToBsonDocument(Object.GetType(), Serializer);
2695
2696 ObjectSerializer Deserializer = this.GetObjectSerializerEx(typeof(GenericObject));
2697
2698 BsonDocumentReader Reader = new(Doc);
2699 BsonDeserializationContext Context = BsonDeserializationContext.CreateRoot(Reader);
2700 BsonDeserializationArgs Args = new()
2701 {
2702 NominalType = typeof(GenericObject)
2703 };
2704
2705 if (Deserializer.Deserialize(Context, Args) is GenericObject Obj)
2706 {
2707 Obj.ArchivingTime = Serializer.GetArchivingTimeDays(Object);
2708 return Task.FromResult(Obj);
2709 }
2710 else
2711 throw new InvalidOperationException("Unable to generalize object.");
2712 }
2713
2719 public Task<object> Specialize(GenericObject Object)
2720 {
2721 if (Object is null)
2722 return Task.FromResult<object>(null);
2723
2724 Type T = Types.GetType(Object.TypeName);
2725 if (T is null)
2726 return Task.FromResult<object>(Object);
2727
2728 ObjectSerializer Serializer = this.GetObjectSerializerEx(typeof(GenericObject));
2729 string CollectionName = Serializer.CollectionName(Object);
2730
2731 BsonDocument Doc = Object.ToBsonDocument(Object.GetType(), Serializer);
2732
2733 Serializer = this.GetObjectSerializerEx(T);
2734
2735 BsonDocumentReader Reader = new(Doc);
2736 BsonDeserializationContext Context = BsonDeserializationContext.CreateRoot(Reader);
2737 BsonDeserializationArgs Args = new()
2738 {
2739 NominalType = typeof(GenericObject)
2740 };
2741
2742 return Task.FromResult<object>(Serializer.Deserialize(Context, Args));
2743 }
2744
2749 public string[] GetExcludedCollections()
2750 {
2751 SortedDictionary<string, bool> Sorted = new(StringComparer.OrdinalIgnoreCase);
2752
2753 lock (this.collections)
2754 {
2755 foreach (IObjectSerializer Serializer in this.serializers.Values)
2756 {
2758 Sorted[ObjectSerializer.CollectionNameConstant] = true;
2759 }
2760 }
2761
2762 string[] Result = new string[Sorted.Count];
2763 Sorted.Keys.CopyTo(Result, 0);
2764
2765 return Result;
2766 }
2767
2768 // TODO:
2769 // * Created field
2770 // * Updated field
2771 // * RegEx fields
2772 // * JavaScript fields
2773 // * Binary fields (BLOBS)
2774 // * Image fields
2775 // * Collection indices.
2776 // * Dictionary<string,T> fields.
2777 // * SortedDictionary<string,T> fields.
2778 // * Aggregates
2779 // * Case insensitive strings.
2780 // * Encrypted properties
2781 }
2782}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
Represents a case-insensitive string.
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
Abstract base class for filters having a single child-filters.
Definition: FilterChild.cs:7
Abstract base class for filters having a variable number of child-filters.
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field greater or equal to a given value.
This filter selects objects that have a named field greater than a given value.
This filter selects objects that have a named field lesser or equal to a given value.
This filter selects objects that have a named field lesser than a given value.
This filter selects objects that have a named field matching a given regular expression.
This filter selects objects that have a named field not equal to a given value.
Abstract base class for all field filters operating on a constant value.
Base class for all filter classes.
Definition: Filter.cs:15
Filter()
Base class for all filter classes.
Definition: Filter.cs:22
This filter selects objects that does not conform to the child-filter provided.
Definition: FilterNot.cs:7
This filter selects objects that conform to any of the child-filters provided.
Definition: FilterOr.cs:10
Task Delete(params object[] Objects)
Deletes a collection of objects in the database.
Task Clear(string CollectionName)
Clears a collection of all objects.
Task UpdateLazy(object Object, ObjectCallback Callback)
Updates an object in the database, if unlocked. If locked, object will be updated at next opportunity...
Task< string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData, ProfilerThread Thread)
Analyzes the database and exports findings to XML.
MongoDBProvider(string DatabaseName, string DefaultCollectionName)
MongoDB database provider, for a local MongoDB database.
Task< string[]> Repair(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData, ProfilerThread Thread)
Analyzes the database and repairs it if necessary. Results are exported to XML.
async Task< object > TryLoadObject(string CollectionName, object ObjectId)
Tries to load an object given its Object ID ObjectId and its collection name CollectionName .
Task< IPage< T > > FindNext< T >(IPage< T > Page)
Finds the next page of objects of a given class T .
Task< T > TryLoadObject< T >(object ObjectId)
Tries to load an object given its Object ID ObjectId and its base type T .
async Task< bool > IsLabel(string CollectionName, string Label)
Checks if a string is a label in a given collection.
Task< string[]> Repair(params string[] CollectionNames)
Repairs a set of collections.
async Task DeleteLazy< T >(int Offset, int MaxCount, string[] SortOrder, ObjectsCallback Callback)
Finds objects of a given class T and deletes them in the same atomic operation.
Task DropCollection(string CollectionName)
Drops a collection, if it exist.
Task< string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData)
Analyzes the database and exports findings to XML.
int ObjectIdByteCount
Number of bytes used by an Object ID.
Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Task< bool > Export(IDatabaseExport Output, string[] CollectionNames)
Performs an export of the database.
Task Iterate< T >(IDatabaseIteration< T > Recipient, string[] CollectionNames)
Performs an iteration of contents of the entire database.
string DefaultCollectionName
Default collection name.
async Task< string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData, bool Repair, ProfilerThread Thread)
Analyzes the database and exports findings to XML.
Task DeleteLazy(object Object, ObjectCallback Callback)
Deletes an object in the database, if unlocked. If locked, object will be deleted at next opportunity...
Task Update(params object[] Objects)
Updates a collection of objects in the database.
async Task Update(object Object)
Updates an object in the database.
IMongoCollection< BsonDocument > DefaultCollection
Default collection.
Task< IPersistedQueue > GetQueue(string QueueName, bool CanBeNull)
Gets a persistent dictionary containing objects in a collection.
Task DeleteLazy(IEnumerable< object > Objects, ObjectsCallback Callback)
Deletes a collection of objects in the database, if unlocked. If locked, objects will be deleted at n...
async Task< IPage< object > > FindFirst(string Collection, int PageSize, params string[] SortOrder)
Finds the first page of objects in a given collection.
Task< bool > Process< T >(IProcessor< T > Processor, int Offset, int MaxCount, params string[] SortOrder)
Processes objects of a given class T .
async Task Insert(IEnumerable< object > Objects)
Inserts a collection of objects into the database.
Task Insert(params object[] Objects)
Inserts a collection of objects into the database.
async Task< string[]> GetDictionaries()
Gets an array of available dictionary collections.
Task< IPersistentDictionary > GetDictionary(string Collection)
Gets a persistent dictionary containing objects in a collection.
string Id
An ID of the files provider. It's unique, and constant during the life-time of the MongoDBProvider cl...
async Task< IEnumerable< object > > FindDelete(string Collection, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Task UpdateLazy(IEnumerable< object > Objects, ObjectsCallback Callback)
Updates a collection of objects in the database, if unlocked. If locked, objects will be updated at n...
Task< bool > Process(IProcessor< object > Processor, string CollectionName, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
Processes objects in a given collection.
async Task Delete(object Object)
Deletes an object in the database.
MongoDBProvider(string HostName, int Port, string DatabaseName, string DefaultCollectionName)
MongoDB database provider.
async Task< string[][]> GetIndices(string CollectionName)
Removes an index from a collection, if one exist.
async Task RemoveIndex(string CollectionName, string[] FieldNames)
Removes an index from a collection, if one exist.
Task< string > GetCollection(Type Type)
Gets the collection corresponding to a given type.
async Task< IEnumerable< T > > FindDelete< T >(int Offset, int MaxCount, params string[] SortOrder)
Finds objects of a given class T and deletes them in the same atomic operation.
Task DeleteLazy(object[] Objects, ObjectsCallback Callback)
Deletes a collection of objects in the database, if unlocked. If locked, objects will be deleted at n...
MongoDBProvider(MongoClientSettings Settings, string DatabaseName, string DefaultCollectionName)
MongoDB database provider.
async Task DeleteLazy(string Collection, int Offset, int MaxCount, Filter Filter, string[] SortOrder, ObjectsCallback Callback)
Finds objects in a given collection and deletes them in the same atomic operation.
IObjectSerializer GetObjectSerializer(Type Type)
Returns a serializer for a given type.
Task< string[]> GetLabels(string Collection)
Gets an array of available labels for a collection.
Task Stop()
Called when processing ends.
async Task< IPage< T > > FindFirst< T >(int PageSize, params string[] SortOrder)
Finds the first page of objects of a given class T .
async Task< object > TryGetObjectId(object Object)
Tries to get the Object ID of an object, if it exists.
Task< IPage< object > > FindNext(IPage< object > Page)
Finds the next page of objects in a given collection.
ObjectSerializer GetObjectSerializerEx(object Object)
Gets the object serializer corresponding to a specific object.
Task< IEnumerable< T > > Find< T >(int Offset, int MaxCount, params string[] SortOrder)
Finds objects of a given class T .
Task< object > Specialize(GenericObject Object)
Creates a specialized representation of a generic object.
Task Start()
Called when processing starts.
async Task< IPage< object > > FindFirst(string Collection, int PageSize, Filter Filter, params string[] SortOrder)
Finds the first page of objects in a given collection.
MongoDBProvider(string HostName, string DatabaseName, string DefaultCollectionName)
MongoDB database provider.
string[] GetExcludedCollections()
Gets an array of collections that should be excluded from backups.
async Task< string[]> GetQueues()
Gets an array of available queue names.
async Task< IEnumerable< object > > FindDelete(string Collection, int Offset, int MaxCount, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Task UpdateLazy(object[] Objects, ObjectsCallback Callback)
Updates a collection of objects in the database, if unlocked. If locked, objects will be updated at n...
Task< string[]> Analyze(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData, bool Repair)
Analyzes the database and exports findings to XML.
Task InsertLazy(IEnumerable< object > Objects, ObjectsCallback Callback)
Inserts an object into the database, if unlocked. If locked, object will be inserted at next opportun...
Task< bool > Process(IProcessor< object > Processor, string CollectionName, int Offset, int MaxCount, params string[] SortOrder)
Processes objects in a given collection.
MongoClient Client
Underlying MongoDB client.
Task Flush()
Persists any pending changes.
Task< IEnumerable< object > > Find(string CollectionName, int Offset, int MaxCount, Filter Filter, params string[] SortOrder)
Finds objects in a given collection.
async Task Insert(object Object)
Inserts an object into the database.
async Task AddIndex(string CollectionName, string[] FieldNames)
Adds an index to a collection, if one does not already exist.
Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Task InsertLazy(object Object, ObjectCallback Callback)
Inserts an object into the database, if unlocked. If locked, object will be inserted at next opportun...
Task< string[]> Repair(ProfilerThread Thread, params string[] CollectionNames)
Repairs a set of collections.
Task< string[]> Repair(XmlWriter Output, string XsltPath, string ProgramDataFolder, bool ExportData)
Analyzes the database and repairs it if necessary. Results are exported to XML.
Task< GenericObject > Generalize(object Object)
Creates a generalized representation of an object.
async Task Update(IEnumerable< object > Objects)
Updates a collection of objects in the database.
async Task Delete(IEnumerable< object > Objects)
Deletes a collection of objects in the database.
Task< IEnumerable< object > > Find(string CollectionName, int Offset, int MaxCount, params string[] SortOrder)
Finds objects in a given collection.
async Task< string[]> GetCollections()
Gets an array of available collections.
async Task< bool > Export(IDatabaseExport Output, string[] CollectionNames, ProfilerThread Thread)
Performs an export of the database.
Task InsertLazy(object[] Objects, ObjectsCallback Callback)
Inserts an object into the database, if unlocked. If locked, object will be inserted at next opportun...
IMongoCollection< BsonDocument > GetCollection(string CollectionName)
Gets a collection.
ObjectSerializer GetObjectSerializerEx(Type Type)
Gets the object serializer corresponding to a specific object.
async Task DeleteLazy(string Collection, int Offset, int MaxCount, string[] SortOrder, ObjectsCallback Callback)
Finds objects in a given collection and deletes them in the same atomic operation.
Task< string > GetCollection(object Object)
Gets the collection corresponding to a given object.
Contains a page of items.
Definition: Page.cs:17
static Guid ObjectIdToGuid(ObjectId ObjectId)
Converts a MongoDB Object ID to a GUID
static ObjectId GuidToObjectId(Guid Guid)
Converts a GUID to a MongoDB Object ID
Serializes a type to BSON, taking into account attributes defined in Waher.Persistence....
virtual string CollectionName(object Object)
Name of collection objects of this type is to be stored in, if available. If not available,...
virtual bool IsDefaultValue(string FieldName, object Value)
Checks if a given field value corresponds to the default value for the corresponding field.
virtual bool TryGetFieldType(string FieldName, object Object, out Type FieldType)
Gets the type of a field or property of an object, given its name.
virtual async Task< ObjectId > GetObjectId(object Value, bool InsertIfNotFound)
Gets the Object ID for a given object.
string ToShortName(string FieldName)
Converts a field name to its corresponding short name. If no explicit short name is available,...
virtual string ObjectIdMemberName
Mamber name of the field or property holding the Object ID, if any. If there are no such member,...
virtual bool HasObjectId(object Value)
If the class has an Object ID.
virtual bool HasObjectIdField
If the class has an Object ID field.
object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
Deserializes a value.
bool BackupCollection
If the corresponding collection should be backed up or not.
static readonly DateTime UnixEpoch
UNIX Epoch, started at 1970-01-01, 00:00:00 (GMT)
This class manages a string dictionary in a persisted storage.
Represents one item in a queue.
Definition: QueuedItem.cs:13
const string QueuedItemCollectionName
Collection name of queued items: QueuedItems
Definition: QueuedItem.cs:17
Generic object. Contains a sequence of properties.
Implements an in-memory cache.
Definition: Cache.cs:17
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:311
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:446
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static Type GetType(string FullName)
Gets a type, given its full name.
Definition: Types.cs:42
static object[] NoParameters
Contains an empty array of parameter values.
Definition: Types.cs:572
static Type[] GetTypesImplementingInterface(string InterfaceFullName)
Gets all types implementing a given interface.
Definition: Types.cs:85
static ConstructorInfo GetDefaultConstructor(Type Type)
Gets the default constructor of a type, if one exists.
Definition: Types.cs:1742
Class that keeps track of events and timing for one thread.
void Exception(System.Exception Exception)
Exception occurred
void NewState(string State)
Thread changes state.
Interface for database providers that can be plugged into the static Database class.
Interface for paginated results.
Definition: IPage.cs:11
Persistent dictionary that can contain more entries than possible in the internal memory.
Interface for processors of objects.
Definition: IProcessor.cs:9
Type ValueType
What type of object is being serialized.
Inteface for collections of persisted queues matching a given database provider.
Task< IPersistedQueue > GetQueue(IDatabaseProvider Provider, string QueueName, bool CanBeNull)
Gets a persisted queue with the specified name. If one is not found, a new one is created.
Interface for database exports that filter objects.
Task< bool > EndCollection()
Is called when a collection is finished.
Task< bool > ReportException(Exception Exception)
Is called when an exception has occurred.
Task< bool > ReportProperty(string PropertyName, object PropertyValue)
Is called when a property is reported.
Task< bool > EndObject()
Is called when an object is finished.
Task< bool > EndIndex()
Is called when an index in a collection is finished.
Task< bool > ReportError(string Message)
Is called when an error is reported.
Task< bool > ReportIndexField(string FieldName, bool Ascending)
Is called when a field in an index is reported.
Task< bool > StartIndex()
Is called when an index in a collection is started.
Task< bool > EndDatabase()
Is called when export of database is finished.
Task< string > StartObject(string ObjectId, string TypeName)
Is called when an object is started.
Task< bool > StartDatabase(IDatabaseProvider Provider)
Is called when export of database is started.
Task< bool > StartCollection(string CollectionName)
Is called when a collection is started.
Interface for iterations of database contents.
delegate void ObjectCallback(object Object)
Method called when a process has completed for an object.
delegate void ObjectsCallback(IEnumerable< object > Objects)
Method called when a process has completed for a set of objects.
Definition: App.xaml.cs:4