diff --git a/Storage/Storage.Test/ObjectControllerTests.cs b/Storage/Storage.Test/ObjectControllerTests.cs index dd53808..c0ded50 100644 --- a/Storage/Storage.Test/ObjectControllerTests.cs +++ b/Storage/Storage.Test/ObjectControllerTests.cs @@ -35,20 +35,19 @@ namespace LeanCloudTests { [Test] public async Task SaveWithPointer() { - AVObject comment = AVObject.Create("Comment"); - comment["content"] = "Hello, Comment"; + AVObject comment = new AVObject("Comment") { + { "content", "Hello, Comment" } + }; - - AVObject post = AVObject.Create("Post"); - post["name"] = "New Post"; - - AVObject category = AVObject.Create("Category"); - post["category"] = category; - + AVObject post = new AVObject("Post") { + { "name", "New Post" }, + { "category", new AVObject("Category") } + }; comment["post"] = post; - AVObject testPost = AVObject.Create("Post"); - testPost["name"] = "Test Post"; + AVObject testPost = new AVObject("Post") { + { "name", "Test Post" } + }; comment["test_post"] = testPost; await comment.SaveAsync(); @@ -126,5 +125,12 @@ namespace LeanCloudTests { await objList.SaveAllAsync(); await AVObject.DeleteAllAsync(objList); } + + [Test] + public void Set() { + AVObject obj = AVObject.Create("Foo"); + obj["hello"] = "world"; + TestContext.Out.WriteAsync(obj["hello"] as string); + } } } diff --git a/Storage/Storage.Test/ObjectTest.cs b/Storage/Storage.Test/ObjectTest.cs index 4fdf29d..1fd98fa 100644 --- a/Storage/Storage.Test/ObjectTest.cs +++ b/Storage/Storage.Test/ObjectTest.cs @@ -1,6 +1,5 @@ using NUnit.Framework; using LeanCloud; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -32,12 +31,13 @@ namespace LeanCloudTests { [Test] public async Task TestMassiveRequest() { + ThreadPool.SetMaxThreads(1, 1); await Task.Run(() => { for (int i = 0; i < 10; i++) { for (int j = 0; j < 50; j++) { AVObject obj = AVObject.Create("Foo"); obj.SaveAsync().ContinueWith(_ => { - TestContext.Out.WriteLine($"{obj.ObjectId} saved"); + TestContext.Out.WriteLine($"{obj.ObjectId} saved at {Thread.CurrentThread.ManagedThreadId}"); }); } Thread.Sleep(1000); diff --git a/Storage/Storage/Internal/Operation/AVRemoveOperation.cs b/Storage/Storage/Internal/Operation/AVRemoveOperation.cs index c9d14a4..23b9f24 100644 --- a/Storage/Storage/Internal/Operation/AVRemoveOperation.cs +++ b/Storage/Storage/Internal/Operation/AVRemoveOperation.cs @@ -18,9 +18,9 @@ namespace LeanCloud.Storage.Internal public object Encode() { return new Dictionary { - {"__op", "Remove"}, - {"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} - }; + { "__op", "Remove" }, + { "objects", PointerOrLocalIdEncoder.Instance.Encode(objects) } + }; } public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) diff --git a/Storage/Storage/Public/AVObject.cs b/Storage/Storage/Public/AVObject.cs index 162fe5a..3acebaa 100644 --- a/Storage/Storage/Public/AVObject.cs +++ b/Storage/Storage/Public/AVObject.cs @@ -378,8 +378,8 @@ string propertyName if (value is AVObject) { // Resolve fetched object. var avObject = value as AVObject; - if (fetchedObject.ContainsKey(avObject.ObjectId)) { - value = fetchedObject[avObject.ObjectId]; + if (fetchedObject.TryGetValue(avObject.ObjectId, out AVObject obj)) { + value = obj; } } newServerData[pair.Key] = value; @@ -542,10 +542,10 @@ string propertyName } } - public virtual Task SaveAsync(bool fetchWhenSave = false, AVQuery query = null, CancellationToken cancellationToken = default) { + public virtual async Task SaveAsync(bool fetchWhenSave = false, AVQuery query = null, CancellationToken cancellationToken = default) { IDictionary currentOperations = null; if (!IsDirty) { - return Task.FromResult(0); + return; } Task deepSaveTask; @@ -555,21 +555,18 @@ string propertyName deepSaveTask = DeepSaveAsync(estimatedData, cancellationToken); } - return deepSaveTask.OnSuccess(_ => { - return ObjectController.SaveAsync(state, - currentOperations, - FetchWhenSave || fetchWhenSave, - query, - cancellationToken); - }).Unwrap().ContinueWith(t => { - if (t.IsFaulted || t.IsCanceled) { - HandleFailedSave(currentOperations); - } else { - var serverState = t.Result; - HandleSave(serverState); - } - return t; - }).Unwrap(); + try { + await deepSaveTask; + IObjectState objState = await ObjectController.SaveAsync(state, + currentOperations, + FetchWhenSave || fetchWhenSave, + query, + cancellationToken); + HandleSave(objState); + } catch (Exception e) { + HandleFailedSave(currentOperations); + throw e; + } } internal virtual Task FetchAsyncInternal( @@ -591,69 +588,60 @@ string propertyName }); } - private static Task DeepSaveAsync(object obj, CancellationToken cancellationToken) { + private static async Task DeepSaveAsync(object obj, CancellationToken cancellationToken) { var objects = new List(); CollectDirtyChildren(obj, objects); - var uniqueObjects = new HashSet(objects, - new IdentityEqualityComparer()); + var uniqueObjects = new HashSet(objects, new IdentityEqualityComparer()); + // 先保存文件对象(后面可以考虑将 AVFile 作为 AVObject 的子类型进行保存) var saveDirtyFileTasks = DeepTraversal(obj, true) .OfType() .Where(f => f.IsDirty) .Select(f => f.SaveAsync(cancellationToken: cancellationToken)).ToList(); + await Task.WhenAll(saveDirtyFileTasks); - return Task.WhenAll(saveDirtyFileTasks).OnSuccess(_ => { - IEnumerable remaining = new List(uniqueObjects); - return InternalExtensions.WhileAsync(() => Task.FromResult(remaining.Any()), () => { - // Partition the objects into two sets: those that can be saved immediately, - // and those that rely on other objects to be created first. - var current = (from item in remaining - where item.CanBeSerialized - select item).ToList(); - var nextBatch = (from item in remaining - where !item.CanBeSerialized - select item).ToList(); - remaining = nextBatch; + IEnumerable remaining = new List(uniqueObjects); + while (remaining.Any()) { + // Partition the objects into two sets: those that can be saved immediately, + // and those that rely on other objects to be created first. + var current = (from item in remaining + where item.CanBeSerialized + select item).ToList(); + var nextBatch = (from item in remaining + where !item.CanBeSerialized + select item).ToList(); + remaining = nextBatch; - if (current.Count == 0) { - // We do cycle-detection when building the list of objects passed to this - // function, so this should never get called. But we should check for it - // anyway, so that we get an exception instead of an infinite loop. - throw new InvalidOperationException( - "Unable to save a AVObject with a relation to a cycle."); + if (current.Count == 0) { + // We do cycle-detection when building the list of objects passed to this + // function, so this should never get called. But we should check for it + // anyway, so that we get an exception instead of an infinite loop. + throw new InvalidOperationException( + "Unable to save a AVObject with a relation to a cycle."); + } + + var states = (from item in current + select item.state).ToList(); + var operationsList = (from item in current + select item.StartSave()).ToList(); + + var saveTasks = ObjectController.SaveAllAsync(states, + operationsList, + cancellationToken); + + try { + var serverStates = await Task.WhenAll(saveTasks); + foreach (var pair in current.Zip(serverStates, (item, state) => new { item, state })) { + pair.item.HandleSave(pair.state); } - - // Save all of the objects in current. - return AVObject.EnqueueForAll(current, toAwait => { - return toAwait.OnSuccess(__ => { - var states = (from item in current - select item.state).ToList(); - var operationsList = (from item in current - select item.StartSave()).ToList(); - - var saveTasks = ObjectController.SaveAllAsync(states, - operationsList, - cancellationToken); - - return Task.WhenAll(saveTasks).ContinueWith(t => { - if (t.IsFaulted || t.IsCanceled) { - foreach (var pair in current.Zip(operationsList, (item, ops) => new { item, ops })) { - pair.item.HandleFailedSave(pair.ops); - } - } else { - var serverStates = t.Result; - foreach (var pair in current.Zip(serverStates, (item, state) => new { item, state })) { - pair.item.HandleSave(pair.state); - } - } - cancellationToken.ThrowIfCancellationRequested(); - return t; - }).Unwrap(); - }).Unwrap().OnSuccess(t => (object)null); - }, cancellationToken); - }); - }).Unwrap(); + } catch (Exception e) { + foreach (var pair in current.Zip(operationsList, (item, ops) => new { item, ops })) { + pair.item.HandleFailedSave(pair.ops); + } + throw e; + } + } } /// @@ -1091,10 +1079,8 @@ string propertyName var value = estimatedData[key]; - // A relation may be deserialized without a parent or key. Either way, - // make sure it's consistent. - var relation = value as AVRelationBase; - if (relation != null) { + if (value is AVRelationBase) { + var relation = value as AVRelationBase; relation.EnsureParentAndKey(this, key); }