From a9ce795c45100b97f1a88c87b7f44c141d37cb1d Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 11 Dec 2019 17:03:23 +0800 Subject: [PATCH] =?UTF-8?q?*=20ObjectTest.cs:=20chore:=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=97=A7=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * AVUser.cs: * AVObject.cs: * SubClassTest.cs: * AVExtensions.cs: * AVObjectExtensions.cs: * AVQueryController.cs: * AVObjectController.cs: --- Storage/Storage.Test/ObjectTest.cs | 1 + Storage/Storage.Test/SubClassTest.cs | 4 +- .../Object/Controller/AVObjectController.cs | 7 - .../Query/Controller/AVQueryController.cs | 3 +- .../Internal/Utilities/AVObjectExtensions.cs | 5 - Storage/Storage/Public/AVExtensions.cs | 24 +- Storage/Storage/Public/AVObject.cs | 902 ++++-------------- Storage/Storage/Public/AVUser.cs | 6 +- 8 files changed, 215 insertions(+), 737 deletions(-) diff --git a/Storage/Storage.Test/ObjectTest.cs b/Storage/Storage.Test/ObjectTest.cs index 4b4b056..2f4d715 100644 --- a/Storage/Storage.Test/ObjectTest.cs +++ b/Storage/Storage.Test/ObjectTest.cs @@ -178,6 +178,7 @@ namespace LeanCloud.Test { await account.DeleteAsync(condition); account = new AVObject("Account") { + { "name", "acl account" }, { "balance", 8 }, }; account.ACL = new AVACL { diff --git a/Storage/Storage.Test/SubClassTest.cs b/Storage/Storage.Test/SubClassTest.cs index aa0255f..3419e49 100644 --- a/Storage/Storage.Test/SubClassTest.cs +++ b/Storage/Storage.Test/SubClassTest.cs @@ -38,8 +38,8 @@ namespace LeanCloud.Test { public async Task SubClass() { AVObject.RegisterSubclass(); AVQuery query = new AVQuery(); - IEnumerable accountList = await query.FindAsync(); - foreach (Account account in accountList) { + IEnumerable accounts = await query.FindAsync(); + foreach (Account account in accounts) { Assert.NotNull(account.Name); Assert.Greater(account.Balance, 0); TestContext.Out.WriteLine($"{account.Name}, {account.Balance}"); diff --git a/Storage/Storage/Internal/Object/Controller/AVObjectController.cs b/Storage/Storage/Internal/Object/Controller/AVObjectController.cs index 132d060..e2ebe8a 100644 --- a/Storage/Storage/Internal/Object/Controller/AVObjectController.cs +++ b/Storage/Storage/Internal/Object/Controller/AVObjectController.cs @@ -92,12 +92,5 @@ namespace LeanCloud.Storage.Internal { } return list; } - - public async Task DeleteAllAsync(IList states, - CancellationToken cancellationToken) { - - // TODO 判断是否全部失败或者网络错误 - - } } } diff --git a/Storage/Storage/Internal/Query/Controller/AVQueryController.cs b/Storage/Storage/Internal/Query/Controller/AVQueryController.cs index d5a0e82..3eb07b9 100644 --- a/Storage/Storage/Internal/Query/Controller/AVQueryController.cs +++ b/Storage/Storage/Internal/Query/Controller/AVQueryController.cs @@ -9,8 +9,7 @@ namespace LeanCloud.Storage.Internal { public class AVQueryController { public async Task> FindAsync(AVQuery query, CancellationToken cancellationToken) where T : AVObject { IList items = await FindAsync>(query.Path, query.BuildParameters(), "results", cancellationToken); - return from item in items - select AVObjectCoder.Instance.Decode(item as IDictionary, AVDecoder.Instance); + return items.Select(item => AVObjectCoder.Instance.Decode(item as IDictionary, AVDecoder.Instance)); } public async Task CountAsync(AVQuery query, CancellationToken cancellationToken) where T : AVObject { diff --git a/Storage/Storage/Internal/Utilities/AVObjectExtensions.cs b/Storage/Storage/Internal/Utilities/AVObjectExtensions.cs index b88a999..5ace09b 100644 --- a/Storage/Storage/Internal/Utilities/AVObjectExtensions.cs +++ b/Storage/Storage/Internal/Utilities/AVObjectExtensions.cs @@ -45,11 +45,6 @@ namespace LeanCloud.Storage.Internal return PointerOrLocalIdEncoder.Instance.EncodeAVObject(obj, false); } - public static void SetIfDifferent(this AVObject obj, string key, T value) - { - obj.SetIfDifferent(key, value); - } - public static IDictionary ServerDataToJSONObjectForSerialization(this AVObject obj) { return obj.ServerDataToJSONObjectForSerialization(); diff --git a/Storage/Storage/Public/AVExtensions.cs b/Storage/Storage/Public/AVExtensions.cs index 33b54bf..5f00067 100644 --- a/Storage/Storage/Public/AVExtensions.cs +++ b/Storage/Storage/Public/AVExtensions.cs @@ -18,9 +18,9 @@ namespace LeanCloud { /// /// The objects to save. /// The cancellation token. - public static Task SaveAllAsync(this IEnumerable objects, bool fetchWhenSave = false, AVQuery query = null, CancellationToken cancellationToken = default) + public static Task SaveAllAsync(this IEnumerable objects, CancellationToken cancellationToken = default) where T : AVObject { - return AVObject.SaveAllAsync(objects, fetchWhenSave, query, cancellationToken); + return AVObject.SaveAllAsync(objects, cancellationToken); } /// @@ -100,25 +100,5 @@ namespace LeanCloud { } return obj.FetchAsyncInternal(queryString, cancellationToken).OnSuccess(t => (T)t.Result); } - - /// - /// If this AVObject has not been fetched (i.e. returns - /// false), fetches this object with the data from the server. - /// - /// The AVObject to fetch. - public static Task FetchIfNeededAsync(this T obj) where T : AVObject { - return obj.FetchIfNeededAsyncInternal(CancellationToken.None).OnSuccess(t => (T)t.Result); - } - - /// - /// If this AVObject has not been fetched (i.e. returns - /// false), fetches this object with the data from the server. - /// - /// The AVObject to fetch. - /// The cancellation token. - public static Task FetchIfNeededAsync(this T obj, CancellationToken cancellationToken) - where T : AVObject { - return obj.FetchIfNeededAsyncInternal(cancellationToken).OnSuccess(t => (T)t.Result); - } } } diff --git a/Storage/Storage/Public/AVObject.cs b/Storage/Storage/Public/AVObject.cs index caa960e..74d7585 100644 --- a/Storage/Storage/Public/AVObject.cs +++ b/Storage/Storage/Public/AVObject.cs @@ -5,44 +5,13 @@ using System.Text; using System.Collections.Generic; using System.Net.Http; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Collections; using System.Collections.Concurrent; namespace LeanCloud { - /// - /// AVObject - /// public class AVObject : IEnumerable> { - internal class Batch { - internal HashSet Objects { - get; set; - } - - public Batch() { - Objects = new HashSet(); - } - - public Batch(IEnumerable objects) : this() { - foreach (AVObject obj in objects) { - Objects.Add(obj); - } - } - - public override string ToString() { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("----------------------------"); - foreach (AVObject obj in Objects) { - sb.AppendLine(obj.ClassName); - } - sb.AppendLine("----------------------------"); - return sb.ToString(); - } - } - - public string ClassName { get { return state.ClassName; @@ -92,8 +61,6 @@ namespace LeanCloud { private static readonly string AutoClassName = "_Automatic"; - internal readonly object mutex = new object(); - internal readonly ConcurrentDictionary operationDict = new ConcurrentDictionary(); private readonly ConcurrentDictionary serverData = new ConcurrentDictionary(); private readonly ConcurrentDictionary estimatedData = new ConcurrentDictionary(); @@ -102,16 +69,12 @@ namespace LeanCloud { private bool hasBeenFetched; private bool dirty; - internal TaskQueue taskQueue = new TaskQueue(); private IObjectState state; - internal void MutateState(Action func) { - lock (mutex) { - state = state.MutatedClone(func); - // Refresh the estimated data. - RebuildEstimatedData(); - } + internal void MutateState(Action func) { + state = state.MutatedClone(func); + RebuildEstimatedData(); } public IObjectState State { @@ -138,28 +101,11 @@ namespace LeanCloud { #region AVObject Creation - /// - /// Constructor for use in AVObject subclasses. Subclasses must specify a AVClassName attribute. - /// protected AVObject() : this(AutoClassName) { } - /// - /// Constructs a new AVObject with no data in it. A AVObject constructed in this way will - /// not have an ObjectId and will not persist to the database until - /// is called. - /// - /// - /// Class names must be alphanumerical plus underscore, and start with a letter. It is recommended - /// to name classes in CamelCaseLikeThis. - /// - /// The className for this AVObject. public AVObject(string className) { - // We use a ThreadLocal rather than passing a parameter so that createWithoutData can do the - // right thing with subclasses. It's ugly and terrible, but it does provide the development - // experience we generally want, so... yeah. Sorry to whomever has to deal with this in the - // future. I pinky-swear we won't make a habit of this -- you believe me, don't you? var isPointer = isCreatingPointer.Value; isCreatingPointer.Value = false; @@ -169,7 +115,6 @@ namespace LeanCloud { if (AutoClassName.Equals(className)) { className = SubclassingController.GetClassName(GetType()); } - // If this is supposed to be created by a factory but wasn't, throw an exception if (!SubclassingController.IsTypeValid(className, GetType())) { throw new ArgumentException( "You must create this type of AVObject using AVObject.Create() or the proper subclass."); @@ -187,25 +132,10 @@ namespace LeanCloud { } } - /// - /// Creates a new AVObject based upon a class name. If the class name is a special type (e.g. - /// for ), then the appropriate type of AVObject is returned. - /// - /// The class of object to create. - /// A new AVObject for the given class name. public static AVObject Create(string className) { return SubclassingController.Instantiate(className); } - /// - /// Creates a reference to an existing AVObject for use in creating associations between - /// AVObjects. Calling on this object will return - /// false until has been called. - /// No network request will be made. - /// - /// The object's class. - /// The object id for the referenced object. - /// A AVObject without data. public static AVObject CreateWithoutData(string className, string objectId) { isCreatingPointer.Value = true; try { @@ -213,8 +143,7 @@ namespace LeanCloud { result.ObjectId = objectId; result.IsDirty = false; if (result.IsDirty) { - throw new InvalidOperationException( - "A AVObject subclass default constructor must not make changes to the object that cause it to be dirty."); + throw new InvalidOperationException("A AVObject subclass default constructor must not make changes to the object that cause it to be dirty."); } return result; } finally { @@ -222,31 +151,14 @@ namespace LeanCloud { } } - /// - /// Creates a new AVObject based upon a given subclass type. - /// - /// A new AVObject for the given class name. public static T Create() where T : AVObject { return (T)SubclassingController.Instantiate(SubclassingController.GetClassName(typeof(T))); } - /// - /// Creates a reference to an existing AVObject for use in creating associations between - /// AVObjects. Calling on this object will return - /// false until has been called. - /// No network request will be made. - /// - /// The object id for the referenced object. - /// A AVObject without data. public static T CreateWithoutData(string objectId) where T : AVObject { return (T)CreateWithoutData(SubclassingController.GetClassName(typeof(T)), objectId); } - /// - /// restore a AVObject of subclass instance from IObjectState. - /// - /// IObjectState after encode from Dictionary. - /// The name of the subclass. public static T FromState(IObjectState state, string defaultClassName) where T : AVObject { string className = state.ClassName ?? defaultClassName; @@ -263,90 +175,34 @@ namespace LeanCloud { } private static string GetFieldForPropertyName(string className, string propertyName) { - SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out string fieldName); - return fieldName; + if (SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out string fieldName)) { + return fieldName; + } + return null; } - /// - /// Sets the value of a property based upon its associated AVFieldName attribute. - /// - /// The new value. - /// The name of the property. - /// The type for the property. - protected virtual void SetProperty(T value, -#if !UNITY - [CallerMemberName] string propertyName = null -#else - string propertyName -#endif -) { + protected virtual void SetProperty(T value, string propertyName) { this[GetFieldForPropertyName(ClassName, propertyName)] = value; } - /// - /// Gets a relation for a property based upon its associated AVFieldName attribute. - /// - /// The AVRelation for the property. - /// The name of the property. - /// The AVObject subclass type of the AVRelation. - protected AVRelation GetRelationProperty( -#if !UNITY -[CallerMemberName] string propertyName = null -#else -string propertyName -#endif -) where T : AVObject { + protected AVRelation GetRelationProperty(string propertyName) where T : AVObject { return GetRelation(GetFieldForPropertyName(ClassName, propertyName)); } - /// - /// Gets the value of a property based upon its associated AVFieldName attribute. - /// - /// The value of the property. - /// The name of the property. - /// The return type of the property. - protected virtual T GetProperty( -#if !UNITY -[CallerMemberName] string propertyName = null -#else -string propertyName -#endif -) { + protected virtual T GetProperty(string propertyName) { return GetProperty(default, propertyName); } - /// - /// Gets the value of a property based upon its associated AVFieldName attribute. - /// - /// The value of the property. - /// The value to return if the property is not present on the AVObject. - /// The name of the property. - /// The return type of the property. - protected virtual T GetProperty(T defaultValue, -#if !UNITY - [CallerMemberName] string propertyName = null -#else - string propertyName -#endif - ) { + protected virtual T GetProperty(T defaultValue, string propertyName) { if (TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out T result)) { return result; } return defaultValue; } - /// - /// Allows subclasses to set values for non-pointer construction. - /// internal virtual void SetDefaultValues() { } - /// - /// Registers a custom subclass type with the LeanCloud SDK, enabling strong-typing of those AVObjects whenever - /// they appear. Subclasses must specify the AVClassName attribute, have a default constructor, and properties - /// backed by AVObject fields should have AVFieldName attributes supplied. - /// - /// The AVObject subclass type to register. public static void RegisterSubclass() where T : AVObject, new() { SubclassingController.RegisterSubclass(typeof(T)); } @@ -355,83 +211,65 @@ string propertyName SubclassingController.UnregisterSubclass(typeof(T)); } - /// - /// Clears any changes to this object made since the last call to . - /// public void Revert() { - lock (mutex) { - if (operationDict.Any()) { - operationDict.Clear(); - RebuildEstimatedData(); - } + if (operationDict.Any()) { + operationDict.Clear(); + RebuildEstimatedData(); } } internal virtual void HandleFetchResult(IObjectState serverState) { - lock (mutex) { - MergeFromServer(serverState); - } + MergeFromServer(serverState); } internal virtual void HandleSave(IObjectState serverState) { - lock (mutex) { - state = state.MutatedClone((objectState) => objectState.Apply(operationDict)); - MergeFromServer(serverState); - } + state = state.MutatedClone((objectState) => objectState.Apply(operationDict)); + MergeFromServer(serverState); } public virtual void MergeFromServer(IObjectState serverState) { // Make a new serverData with fetched values. var newServerData = serverState.ToDictionary(t => t.Key, t => t.Value); - lock (mutex) { - // Trigger handler based on serverState - if (serverState.ObjectId != null) { - // If the objectId is being merged in, consider this object to be fetched. - hasBeenFetched = true; - } + // Trigger handler based on serverState + hasBeenFetched |= serverState.ObjectId != null; - // We cache the fetched object because subsequent Save operation might flush - // the fetched objects into Pointers. - //IDictionary fetchedObject = CollectFetchedObjects(); + // We cache the fetched object because subsequent Save operation might flush + // the fetched objects into Pointers. + //IDictionary fetchedObject = CollectFetchedObjects(); - //foreach (var pair in serverState) { - // var value = pair.Value; - // if (value is AVObject) { - // // Resolve fetched object. - // var avObject = value as AVObject; - // if (fetchedObject.TryGetValue(avObject.ObjectId, out AVObject obj)) { - // value = obj; - // } - // } - // newServerData[pair.Key] = value; - //} + //foreach (var pair in serverState) { + // var value = pair.Value; + // if (value is AVObject) { + // // Resolve fetched object. + // var avObject = value as AVObject; + // if (fetchedObject.TryGetValue(avObject.ObjectId, out AVObject obj)) { + // value = obj; + // } + // } + // newServerData[pair.Key] = value; + //} - IsDirty = false; - serverState = serverState.MutatedClone(mutableClone => { - mutableClone.ServerData = newServerData; - }); - MutateState(mutableClone => { - mutableClone.Apply(serverState); - }); - } + IsDirty = false; + serverState = serverState.MutatedClone(mutableClone => { + mutableClone.ServerData = newServerData; + }); + MutateState(mutableClone => { + mutableClone.Apply(serverState); + }); } internal void MergeFromObject(AVObject other) { - lock (mutex) { - if (this == other) { - return; - } + if (this == other) { + return; } operationDict.Clear(); foreach (KeyValuePair entry in other.operationDict) { operationDict.AddOrUpdate(entry.Key, entry.Value, (key, value) => value); } + state = other.State; - lock (mutex) { - state = other.State; - } RebuildEstimatedData(); } @@ -448,11 +286,9 @@ string propertyName internal IDictionary EncodeForSaving(IDictionary data) { var result = new Dictionary(); - lock (this.mutex) { - foreach (var key in data.Keys) { - var value = data[key]; - result.Add(key, PointerOrLocalIdEncoder.Instance.Encode(value)); - } + foreach (var key in data.Keys) { + var value = data[key]; + result.Add(key, PointerOrLocalIdEncoder.Instance.Encode(value)); } return result; @@ -476,11 +312,7 @@ string propertyName HandleSave(result); } - /// - /// Saves each object in the provided list. - /// - /// The objects to save. - public static async Task SaveAllAsync(IEnumerable objects, bool fetchWhenSave = false, AVQuery query = null, CancellationToken cancellationToken = default) + public static async Task SaveAllAsync(IEnumerable objects, CancellationToken cancellationToken = default) where T : AVObject { foreach (T obj in objects) { if (HasCircleReference(obj, new HashSet())) { @@ -507,169 +339,82 @@ string propertyName } } - internal virtual Task FetchAsyncInternal( - Task toAwait, - IDictionary queryString, - CancellationToken cancellationToken) { - return toAwait.OnSuccess(_ => { - if (ObjectId == null) { - throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server."); - } - if (queryString == null) { - queryString = new Dictionary(); - } - - return ObjectController.FetchAsync(state, queryString, cancellationToken); - }).Unwrap().OnSuccess(t => { - HandleFetchResult(t.Result); - return this; - }); + internal virtual async Task FetchAsyncInternal(IDictionary queryString, CancellationToken cancellationToken = default) { + if (ObjectId == null) { + throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server."); + } + if (queryString == null) { + queryString = new Dictionary(); + } + IObjectState objectState = await ObjectController.FetchAsync(state, queryString, cancellationToken); + HandleFetchResult(objectState); + return this; } #endregion #region Fetch Object(s) - /// - /// Fetches this object with the data from the server. - /// - /// The cancellation token. - internal Task FetchAsyncInternal(CancellationToken cancellationToken) { - return FetchAsyncInternal(null, cancellationToken); - } - - internal Task FetchAsyncInternal(IDictionary queryString, CancellationToken cancellationToken) { - return taskQueue.Enqueue(toAwait => FetchAsyncInternal(toAwait, queryString, cancellationToken), - cancellationToken); - } - - internal Task FetchIfNeededAsyncInternal( - Task toAwait, CancellationToken cancellationToken) { - if (!IsDataAvailable) { - return FetchAsyncInternal(toAwait, null, cancellationToken); - } - return Task.FromResult(this); - } - - /// - /// If this AVObject has not been fetched (i.e. returns - /// false), fetches this object with the data from the server. - /// - /// The cancellation token. - internal Task FetchIfNeededAsyncInternal(CancellationToken cancellationToken) { - return taskQueue.Enqueue(toAwait => FetchIfNeededAsyncInternal(toAwait, cancellationToken), - cancellationToken); - } - - /// - /// Fetches all of the objects that don't have data in the provided list. - /// - /// The list passed in for convenience. public static Task> FetchAllIfNeededAsync( - IEnumerable objects) where T : AVObject { - return FetchAllIfNeededAsync(objects, CancellationToken.None); + IEnumerable objects, CancellationToken cancellationToken = default) where T : AVObject { + return FetchAllInternalAsync(objects, false, cancellationToken); } - /// - /// Fetches all of the objects that don't have data in the provided list. - /// - /// The objects to fetch. - /// The cancellation token. - /// The list passed in for convenience. - public static Task> FetchAllIfNeededAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : AVObject { - return EnqueueForAll(objects.Cast(), (Task toAwait) => { - return FetchAllInternalAsync(objects, false, toAwait, cancellationToken); - }, cancellationToken); + public static Task> FetchAllAsync(IEnumerable objects, CancellationToken cancellationToken = default) where T : AVObject { + return FetchAllInternalAsync(objects, true, cancellationToken); } - /// - /// Fetches all of the objects in the provided list. - /// - /// The objects to fetch. - /// The list passed in for convenience. - public static Task> FetchAllAsync( - IEnumerable objects) where T : AVObject { - return FetchAllAsync(objects, CancellationToken.None); - } - - /// - /// Fetches all of the objects in the provided list. - /// - /// The objects to fetch. - /// The cancellation token. - /// The list passed in for convenience. - public static Task> FetchAllAsync( - IEnumerable objects, CancellationToken cancellationToken) where T : AVObject { - return EnqueueForAll(objects.Cast(), (Task toAwait) => { - return FetchAllInternalAsync(objects, true, toAwait, cancellationToken); - }, cancellationToken); - } - - /// - /// Fetches all of the objects in the list. - /// - /// The objects to fetch. - /// If false, only objects without data will be fetched. - /// A task to await before starting. - /// The cancellation token. - /// The list passed in for convenience. private static Task> FetchAllInternalAsync( - IEnumerable objects, bool force, Task toAwait, CancellationToken cancellationToken) where T : AVObject { - return toAwait.OnSuccess(_ => { - if (objects.Any(obj => { return obj.state.ObjectId == null; })) { - throw new InvalidOperationException("You cannot fetch objects that haven't already been saved."); - } + IEnumerable objects, bool force, CancellationToken cancellationToken) where T : AVObject { - var objectsToFetch = (from obj in objects - where force || !obj.IsDataAvailable - select obj).ToList(); + if (objects.Any(obj => { return obj.state.ObjectId == null; })) { + throw new InvalidOperationException("You cannot fetch objects that haven't already been saved."); + } - if (objectsToFetch.Count == 0) { - return Task.FromResult(objects); - } + var objectsToFetch = (from obj in objects + where force || !obj.IsDataAvailable + select obj).ToList(); - // Do one Find for each class. - var findsByClass = - (from obj in objectsToFetch - group obj.ObjectId by obj.ClassName into classGroup - where classGroup.Count() > 0 - select new { - ClassName = classGroup.Key, - FindTask = new AVQuery(classGroup.Key) - .WhereContainedIn("objectId", classGroup) - .FindAsync(cancellationToken) - }).ToDictionary(pair => pair.ClassName, pair => pair.FindTask); + if (objectsToFetch.Count == 0) { + return Task.FromResult(objects); + } - // Wait for all the Finds to complete. - return Task.WhenAll(findsByClass.Values.ToList()).OnSuccess(__ => { - if (cancellationToken.IsCancellationRequested) { - return objects; - } - - // Merge the data from the Finds into the input objects. - var pairs = from obj in objectsToFetch - from result in findsByClass[obj.ClassName].Result - where result.ObjectId == obj.ObjectId - select new { obj, result }; - foreach (var pair in pairs) { - pair.obj.MergeFromObject(pair.result); - pair.obj.hasBeenFetched = true; - } + // Do one Find for each class. + var findsByClass = + (from obj in objectsToFetch + group obj.ObjectId by obj.ClassName into classGroup + where classGroup.Any() + select new { + ClassName = classGroup.Key, + FindTask = new AVQuery(classGroup.Key) + .WhereContainedIn("objectId", classGroup) + .FindAsync(cancellationToken) + }).ToDictionary(pair => pair.ClassName, pair => pair.FindTask); + // Wait for all the Finds to complete. + return Task.WhenAll(findsByClass.Values.ToList()).OnSuccess(__ => { + if (cancellationToken.IsCancellationRequested) { return objects; - }); - }).Unwrap(); + } + + // Merge the data from the Finds into the input objects. + var pairs = from obj in objectsToFetch + from result in findsByClass[obj.ClassName].Result + where result.ObjectId == obj.ObjectId + select new { obj, result }; + foreach (var pair in pairs) { + pair.obj.MergeFromObject(pair.result); + pair.obj.hasBeenFetched = true; + } + + return objects; + }); } #endregion #region Delete Object - /// - /// Deletes this object on the server. - /// - /// The cancellation token. public virtual async Task DeleteAsync(AVQuery query = null, CancellationToken cancellationToken = default) { if (ObjectId == null) { return; @@ -688,10 +433,6 @@ string propertyName IsDirty = true; } - /// - /// Deletes each object in the provided list. - /// - /// The objects to delete. public static async Task DeleteAllAsync(IEnumerable objects, CancellationToken cancellationToken = default) where T : AVObject { var uniqueObjects = new HashSet(objects.OfType().ToList(), @@ -714,319 +455,134 @@ string propertyName #endregion - /// - /// Adds a task to the queue for all of the given objects. - /// - private static Task EnqueueForAll(IEnumerable objects, - Func> taskStart, CancellationToken cancellationToken) { - // The task that will be complete when all of the child queues indicate they're ready to start. - var readyToStart = new TaskCompletionSource(); - - // First, we need to lock the mutex for the queue for every object. We have to hold this - // from at least when taskStart() is called to when obj.taskQueue enqueue is called, so - // that saves actually get executed in the order they were setup by taskStart(). - // The locks have to be sorted so that we always acquire them in the same order. - // Otherwise, there's some risk of deadlock. - var lockSet = new LockSet(objects.Select(o => o.taskQueue.Mutex)); - - lockSet.Enter(); - try { - - // The task produced by taskStart. By running this immediately, we allow everything prior - // to toAwait to run before waiting for all of the queues on all of the objects. - Task fullTask = taskStart(readyToStart.Task); - - // Add fullTask to each of the objects' queues. - var childTasks = new List(); - foreach (AVObject obj in objects) { - obj.taskQueue.Enqueue((Task task) => { - childTasks.Add(task); - return fullTask; - }, cancellationToken); - } - - // When all of the objects' queues are ready, signal fullTask that it's ready to go on. - Task.WhenAll(childTasks.ToArray()).ContinueWith((Task task) => { - readyToStart.SetResult(null); - }); - - return fullTask; - } finally { - lockSet.Exit(); - } - } - - /// - /// Removes a key from the object's data if it exists. - /// - /// The key to remove. public virtual void Remove(string key) { - lock (mutex) { - CheckKeyIsMutable(key); - - PerformOperation(key, AVDeleteOperation.Instance); - } + CheckKeyIsMutable(key); + PerformOperation(key, AVDeleteOperation.Instance); } private IEnumerable ApplyOperations(IDictionary operations, IDictionary map) { List appliedKeys = new List(); - lock (mutex) { - foreach (var pair in operations) { - map.TryGetValue(pair.Key, out object oldValue); - var newValue = pair.Value.Apply(oldValue, pair.Key); - if (newValue != AVDeleteOperation.DeleteToken) { - map[pair.Key] = newValue; - } else { - map.Remove(pair.Key); - } - appliedKeys.Add(pair.Key); + foreach (var pair in operations) { + map.TryGetValue(pair.Key, out object oldValue); + var newValue = pair.Value.Apply(oldValue, pair.Key); + if (newValue != AVDeleteOperation.DeleteToken) { + map[pair.Key] = newValue; + } else { + map.Remove(pair.Key); } + appliedKeys.Add(pair.Key); } return appliedKeys; } - /// - /// Regenerates the estimatedData map from the serverData and operations. - /// internal void RebuildEstimatedData() { estimatedData.Clear(); ApplyOperations(operationDict, estimatedData); } - /// - /// PerformOperation is like setting a value at an index, but instead of - /// just taking a new value, it takes a AVFieldOperation that modifies the value. - /// internal void PerformOperation(string key, IAVFieldOperation operation) { - lock (mutex) { - estimatedData.TryGetValue(key, out object oldValue); - object newValue = operation.Apply(oldValue, key); - if (newValue != AVDeleteOperation.DeleteToken) { - estimatedData[key] = newValue; - } else { - estimatedData.TryRemove(key, out _); - } - - if (operationDict.TryGetValue(key, out IAVFieldOperation oldOperation)) { - operation = operation.MergeWithPrevious(oldOperation); - } - operationDict[key] = operation; + estimatedData.TryGetValue(key, out object oldValue); + object newValue = operation.Apply(oldValue, key); + if (newValue != AVDeleteOperation.DeleteToken) { + estimatedData[key] = newValue; + } else { + estimatedData.TryRemove(key, out _); } + + if (operationDict.TryGetValue(key, out IAVFieldOperation oldOperation)) { + operation = operation.MergeWithPrevious(oldOperation); + } + operationDict[key] = operation; } - /// - /// Override to run validations on key/value pairs. Make sure to still - /// call the base version. - /// internal virtual void OnSettingValue(ref string key, ref object value) { if (key == null) { throw new ArgumentNullException(nameof(key)); } } - /// - /// Gets or sets a value on the object. It is recommended to name - /// keys in partialCamelCaseLikeThis. - /// - /// The key for the object. Keys must be alphanumeric plus underscore - /// and start with a letter. - /// The property is - /// retrieved and is not found. - /// The value for the key. virtual public object this[string key] { get { - lock (mutex) { - CheckGetAccess(key); + CheckGetAccess(key); - if (!estimatedData.TryGetValue(key, out object value)) { - value = state[key]; - } - - if (value is AVRelationBase) { - var relation = value as AVRelationBase; - relation.EnsureParentAndKey(this, key); - } - - return value; + if (!estimatedData.TryGetValue(key, out object value)) { + value = state[key]; } + + if (value is AVRelationBase) { + var relation = value as AVRelationBase; + relation.EnsureParentAndKey(this, key); + } + + return value; } set { - lock (mutex) { - CheckKeyIsMutable(key); - - Set(key, value); - } - } - } - - /// - /// Perform Set internally which is not gated by mutability check. - /// - /// key for the object. - /// the value for the key. - internal void Set(string key, object value) { - lock (mutex) { - OnSettingValue(ref key, ref value); - PerformOperation(key, new AVSetOperation(value)); - } - } - - internal void SetIfDifferent(string key, T value) { - bool hasCurrent = TryGetValue(key, out T current); - if (value == null) { - if (hasCurrent) { - PerformOperation(key, AVDeleteOperation.Instance); - } - return; - } - if (!hasCurrent || !value.Equals(current)) { + CheckKeyIsMutable(key); Set(key, value); } } + internal void Set(string key, object value) { + OnSettingValue(ref key, ref value); + PerformOperation(key, new AVSetOperation(value)); + } + #region Atomic Increment - /// - /// Atomically increments the given key by 1. - /// - /// The key to increment. public void Increment(string key) { Increment(key, 1); } - /// - /// Atomically increments the given key by the given number. - /// - /// The key to increment. - /// The amount to increment by. public void Increment(string key, long amount) { - lock (mutex) { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVIncrementOperation(amount)); - } + CheckKeyIsMutable(key); + PerformOperation(key, new AVIncrementOperation(amount)); } - /// - /// Atomically increments the given key by the given number. - /// - /// The key to increment. - /// The amount to increment by. public void Increment(string key, double amount) { - lock (mutex) { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVIncrementOperation(amount)); - } + CheckKeyIsMutable(key); + PerformOperation(key, new AVIncrementOperation(amount)); } #endregion - /// - /// Atomically adds an object to the end of the list associated with the given key. - /// - /// The key. - /// The object to add. public void AddToList(string key, object value) { AddRangeToList(key, new[] { value }); } - /// - /// Atomically adds objects to the end of the list associated with the given key. - /// - /// The key. - /// The objects to add. public void AddRangeToList(string key, IEnumerable values) { - lock (mutex) { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVAddOperation(values.Cast())); - } + CheckKeyIsMutable(key); + PerformOperation(key, new AVAddOperation(values.Cast())); } - /// - /// Atomically adds an object to the end of the list associated with the given key, - /// only if it is not already present in the list. The position of the insert is not - /// guaranteed. - /// - /// The key. - /// The object to add. public void AddUniqueToList(string key, object value) { AddRangeUniqueToList(key, new object[] { value }); } - /// - /// Atomically adds objects to the end of the list associated with the given key, - /// only if they are not already present in the list. The position of the inserts are not - /// guaranteed. - /// - /// The key. - /// The objects to add. public void AddRangeUniqueToList(string key, IEnumerable values) { - lock (mutex) { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVAddUniqueOperation(values.Cast())); - } + CheckKeyIsMutable(key); + PerformOperation(key, new AVAddUniqueOperation(values.Cast())); } - /// - /// Atomically removes all instances of the objects in - /// from the list associated with the given key. - /// - /// The key. - /// The objects to remove. public void RemoveAllFromList(string key, IEnumerable values) { - lock (mutex) { - CheckKeyIsMutable(key); - - PerformOperation(key, new AVRemoveOperation(values.Cast())); - } + CheckKeyIsMutable(key); + PerformOperation(key, new AVRemoveOperation(values.Cast())); } - /// - /// Returns whether this object has a particular key. - /// - /// The key to check for public bool ContainsKey(string key) { - lock (mutex) { - return estimatedData.ContainsKey(key); - } + return estimatedData.ContainsKey(key) || state.ContainsKey(key); } - /// - /// Gets a value for the key of a particular type. - /// The type to convert the value to. Supported types are - /// AVObject and its descendents, LeanCloud types such as AVRelation and AVGeopoint, - /// primitive types,IList<T>, IDictionary<string, T>, and strings. - /// The key of the element to get. - /// The property is - /// retrieved and is not found. - /// public T Get(string key) { return Conversion.To(this[key]); } - /// - /// Access or create a Relation value for a key. - /// - /// The type of object to create a relation for. - /// The key for the relation field. - /// A AVRelation for the key. public AVRelation GetRelation(string key) where T : AVObject { // All the sanity checking is done when add or remove is called. TryGetValue(key, out AVRelation relation); return relation ?? new AVRelation(this, key); } - /// - /// Get relation revserse query. - /// - /// AVObject - /// parent className - /// key - /// public AVQuery GetRelationRevserseQuery(string parentClassName, string key) where T : AVObject { if (string.IsNullOrEmpty(parentClassName)) { throw new ArgumentNullException(nameof(parentClassName), "can not query a relation without parentClassName."); @@ -1037,54 +593,34 @@ string propertyName return new AVQuery(parentClassName).WhereEqualTo(key, this); } - /// - /// Populates result with the value for the key, if possible. - /// - /// The desired type for the value. - /// The key to retrieve a value for. - /// The value for the given key, converted to the - /// requested type, or null if unsuccessful. - /// true if the lookup and conversion succeeded, otherwise - /// false. public virtual bool TryGetValue(string key, out T result) { - lock (mutex) { - if (ContainsKey(key)) { - try { - var temp = Conversion.To(this[key]); - result = temp; - return true; - } catch (InvalidCastException) { - result = default(T); - return false; - } + if (ContainsKey(key)) { + try { + var temp = Conversion.To(this[key]); + result = temp; + return true; + } catch (InvalidCastException) { + result = default; + return false; } - result = default(T); - return false; } + result = default; + return false; } - /// - /// Gets whether the AVObject has been fetched. - /// public bool IsDataAvailable { get { - lock (mutex) { - return hasBeenFetched; - } + return hasBeenFetched; } } private bool CheckIsDataAvailable(string key) { - lock (mutex) { - return IsDataAvailable || estimatedData.ContainsKey(key); - } + return IsDataAvailable || estimatedData.ContainsKey(key); } private void CheckGetAccess(string key) { - lock (mutex) { - if (!CheckIsDataAvailable(key)) { - throw new InvalidOperationException("AVObject has no data for this key. Call FetchIfNeededAsync() to get the data."); - } + if (!CheckIsDataAvailable(key)) { + throw new InvalidOperationException("AVObject has no data for this key. Call FetchIfNeededAsync() to get the data."); } } @@ -1098,101 +634,48 @@ string propertyName return true; } - /// - /// A helper function for checking whether two AVObjects point to - /// the same object in the cloud. - /// public bool HasSameId(AVObject other) { - lock (mutex) { - return other != null && + return other != null && object.Equals(ClassName, other.ClassName) && object.Equals(ObjectId, other.ObjectId); - } } - /// - /// Indicates whether this AVObject has unsaved changes. - /// public bool IsDirty { get { - lock (mutex) { return CheckIsDirty(true); } + return CheckIsDirty(); } internal set { - lock (mutex) { - dirty = value; - } + dirty = value; } } - /// - /// Indicates whether key is unsaved for this AVObject. - /// - /// The key to check for. - /// true if the key has been altered and not saved yet, otherwise - /// false. public bool IsKeyDirty(string key) { - lock (mutex) { - return operationDict.ContainsKey(key); - } + return operationDict.ContainsKey(key); } - private bool CheckIsDirty(bool considerChildren) { - lock (mutex) { - return dirty || operationDict.Count > 0; - } + private bool CheckIsDirty() { + return dirty || operationDict.Count > 0; } - - /// - /// Sets the objectId without marking dirty. - /// - /// The new objectId private void SetObjectIdInternal(string objectId) { - lock (mutex) { - MutateState(mutableClone => { - mutableClone.ObjectId = objectId; - }); - } + MutateState(mutableClone => { + mutableClone.ObjectId = objectId; + }); } - /// - /// Adds a value for the given key, throwing an Exception if the key - /// already has a value. - /// - /// - /// This allows you to use collection initialization syntax when creating AVObjects, - /// such as: - /// - /// var obj = new AVObject("MyType") - /// { - /// {"name", "foo"}, - /// {"count", 10}, - /// {"found", false} - /// }; - /// - /// - /// The key for which a value should be set. - /// The value for the key. public void Add(string key, object value) { - lock (mutex) { - if (this.ContainsKey(key)) { - throw new ArgumentException("Key already exists", key); - } - this[key] = value; + if (ContainsKey(key)) { + throw new ArgumentException("Key already exists", key); } + this[key] = value; } - IEnumerator> IEnumerable> - .GetEnumerator() { - lock (mutex) { - return estimatedData.GetEnumerator(); - } + IEnumerator> IEnumerable>.GetEnumerator() { + return estimatedData.GetEnumerator(); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - lock (mutex) { - return ((IEnumerable>)this).GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() { + return ((IEnumerable>)this).GetEnumerator(); } public static AVQuery GetQuery(string className) @@ -1266,5 +749,34 @@ string propertyName } #endregion + + /// + /// 保存 AVObject 时用到的辅助批次工具类 + /// + internal class Batch { + internal HashSet Objects { + get; set; + } + + public Batch() { + Objects = new HashSet(); + } + + public Batch(IEnumerable objects) : this() { + foreach (AVObject obj in objects) { + Objects.Add(obj); + } + } + + public override string ToString() { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("----------------------------"); + foreach (AVObject obj in Objects) { + sb.AppendLine(obj.ClassName); + } + sb.AppendLine("----------------------------"); + return sb.ToString(); + } + } } } diff --git a/Storage/Storage/Public/AVUser.cs b/Storage/Storage/Public/AVUser.cs index 7f46e0e..5521060 100644 --- a/Storage/Storage/Public/AVUser.cs +++ b/Storage/Storage/Public/AVUser.cs @@ -151,10 +151,8 @@ namespace LeanCloud { /// /// public async Task IsAuthenticatedAsync() { - lock (mutex) { - if (SessionToken == null || CurrentUser == null || CurrentUser.ObjectId != ObjectId) { - return false; - } + if (SessionToken == null || CurrentUser == null || CurrentUser.ObjectId != ObjectId) { + return false; } var command = new AVCommand { Path = $"users/me?session_token={SessionToken}",