* ObjectControllerTests.cs: chore: 简化 Object 保存逻辑

* ObjectTest.cs:
* AVObject.cs:
* AVRemoveOperation.cs:
oneRain 2019-09-09 16:34:49 +08:00
parent b0cf1caa5e
commit bc5d396ab9
4 changed files with 83 additions and 91 deletions

View File

@ -35,20 +35,19 @@ namespace LeanCloudTests {
[Test] [Test]
public async Task SaveWithPointer() { public async Task SaveWithPointer() {
AVObject comment = AVObject.Create("Comment"); AVObject comment = new AVObject("Comment") {
comment["content"] = "Hello, 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; comment["post"] = post;
AVObject testPost = AVObject.Create("Post"); AVObject testPost = new AVObject("Post") {
testPost["name"] = "Test Post"; { "name", "Test Post" }
};
comment["test_post"] = testPost; comment["test_post"] = testPost;
await comment.SaveAsync(); await comment.SaveAsync();
@ -126,5 +125,12 @@ namespace LeanCloudTests {
await objList.SaveAllAsync(); await objList.SaveAllAsync();
await AVObject.DeleteAllAsync(objList); await AVObject.DeleteAllAsync(objList);
} }
[Test]
public void Set() {
AVObject obj = AVObject.Create("Foo");
obj["hello"] = "world";
TestContext.Out.WriteAsync(obj["hello"] as string);
}
} }
} }

View File

@ -1,6 +1,5 @@
using NUnit.Framework; using NUnit.Framework;
using LeanCloud; using LeanCloud;
using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -32,12 +31,13 @@ namespace LeanCloudTests {
[Test] [Test]
public async Task TestMassiveRequest() { public async Task TestMassiveRequest() {
ThreadPool.SetMaxThreads(1, 1);
await Task.Run(() => { await Task.Run(() => {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
for (int j = 0; j < 50; j++) { for (int j = 0; j < 50; j++) {
AVObject obj = AVObject.Create("Foo"); AVObject obj = AVObject.Create("Foo");
obj.SaveAsync().ContinueWith(_ => { obj.SaveAsync().ContinueWith(_ => {
TestContext.Out.WriteLine($"{obj.ObjectId} saved"); TestContext.Out.WriteLine($"{obj.ObjectId} saved at {Thread.CurrentThread.ManagedThreadId}");
}); });
} }
Thread.Sleep(1000); Thread.Sleep(1000);

View File

@ -18,9 +18,9 @@ namespace LeanCloud.Storage.Internal
public object Encode() public object Encode()
{ {
return new Dictionary<string, object> { return new Dictionary<string, object> {
{"__op", "Remove"}, { "__op", "Remove" },
{"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)} { "objects", PointerOrLocalIdEncoder.Instance.Encode(objects) }
}; };
} }
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous)

View File

@ -378,8 +378,8 @@ string propertyName
if (value is AVObject) { if (value is AVObject) {
// Resolve fetched object. // Resolve fetched object.
var avObject = value as AVObject; var avObject = value as AVObject;
if (fetchedObject.ContainsKey(avObject.ObjectId)) { if (fetchedObject.TryGetValue(avObject.ObjectId, out AVObject obj)) {
value = fetchedObject[avObject.ObjectId]; value = obj;
} }
} }
newServerData[pair.Key] = value; newServerData[pair.Key] = value;
@ -542,10 +542,10 @@ string propertyName
} }
} }
public virtual Task SaveAsync(bool fetchWhenSave = false, AVQuery<AVObject> query = null, CancellationToken cancellationToken = default) { public virtual async Task SaveAsync(bool fetchWhenSave = false, AVQuery<AVObject> query = null, CancellationToken cancellationToken = default) {
IDictionary<string, IAVFieldOperation> currentOperations = null; IDictionary<string, IAVFieldOperation> currentOperations = null;
if (!IsDirty) { if (!IsDirty) {
return Task.FromResult(0); return;
} }
Task deepSaveTask; Task deepSaveTask;
@ -555,21 +555,18 @@ string propertyName
deepSaveTask = DeepSaveAsync(estimatedData, cancellationToken); deepSaveTask = DeepSaveAsync(estimatedData, cancellationToken);
} }
return deepSaveTask.OnSuccess(_ => { try {
return ObjectController.SaveAsync(state, await deepSaveTask;
currentOperations, IObjectState objState = await ObjectController.SaveAsync(state,
FetchWhenSave || fetchWhenSave, currentOperations,
query, FetchWhenSave || fetchWhenSave,
cancellationToken); query,
}).Unwrap().ContinueWith(t => { cancellationToken);
if (t.IsFaulted || t.IsCanceled) { HandleSave(objState);
HandleFailedSave(currentOperations); } catch (Exception e) {
} else { HandleFailedSave(currentOperations);
var serverState = t.Result; throw e;
HandleSave(serverState); }
}
return t;
}).Unwrap();
} }
internal virtual Task<AVObject> FetchAsyncInternal( internal virtual Task<AVObject> 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<AVObject>(); var objects = new List<AVObject>();
CollectDirtyChildren(obj, objects); CollectDirtyChildren(obj, objects);
var uniqueObjects = new HashSet<AVObject>(objects, var uniqueObjects = new HashSet<AVObject>(objects, new IdentityEqualityComparer<AVObject>());
new IdentityEqualityComparer<AVObject>());
// 先保存文件对象(后面可以考虑将 AVFile 作为 AVObject 的子类型进行保存)
var saveDirtyFileTasks = DeepTraversal(obj, true) var saveDirtyFileTasks = DeepTraversal(obj, true)
.OfType<AVFile>() .OfType<AVFile>()
.Where(f => f.IsDirty) .Where(f => f.IsDirty)
.Select(f => f.SaveAsync(cancellationToken: cancellationToken)).ToList(); .Select(f => f.SaveAsync(cancellationToken: cancellationToken)).ToList();
await Task.WhenAll(saveDirtyFileTasks);
return Task.WhenAll(saveDirtyFileTasks).OnSuccess(_ => { IEnumerable<AVObject> remaining = new List<AVObject>(uniqueObjects);
IEnumerable<AVObject> remaining = new List<AVObject>(uniqueObjects); while (remaining.Any()) {
return InternalExtensions.WhileAsync(() => Task.FromResult(remaining.Any()), () => { // Partition the objects into two sets: those that can be saved immediately,
// Partition the objects into two sets: those that can be saved immediately, // and those that rely on other objects to be created first.
// and those that rely on other objects to be created first. var current = (from item in remaining
var current = (from item in remaining where item.CanBeSerialized
where item.CanBeSerialized select item).ToList();
select item).ToList(); var nextBatch = (from item in remaining
var nextBatch = (from item in remaining where !item.CanBeSerialized
where !item.CanBeSerialized select item).ToList();
select item).ToList(); remaining = nextBatch;
remaining = nextBatch;
if (current.Count == 0) { if (current.Count == 0) {
// We do cycle-detection when building the list of objects passed to this // 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 // 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. // anyway, so that we get an exception instead of an infinite loop.
throw new InvalidOperationException( throw new InvalidOperationException(
"Unable to save a AVObject with a relation to a cycle."); "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);
} }
} catch (Exception e) {
// Save all of the objects in current. foreach (var pair in current.Zip(operationsList, (item, ops) => new { item, ops })) {
return AVObject.EnqueueForAll<object>(current, toAwait => { pair.item.HandleFailedSave(pair.ops);
return toAwait.OnSuccess(__ => { }
var states = (from item in current throw e;
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();
} }
/// <summary> /// <summary>
@ -1091,10 +1079,8 @@ string propertyName
var value = estimatedData[key]; var value = estimatedData[key];
// A relation may be deserialized without a parent or key. Either way, if (value is AVRelationBase) {
// make sure it's consistent. var relation = value as AVRelationBase;
var relation = value as AVRelationBase;
if (relation != null) {
relation.EnsureParentAndKey(this, key); relation.EnsureParentAndKey(this, key);
} }