* ObjectTest.cs: chore: 完善 Object 模块和测试

* LCQuery.cs:
* LCObject.cs:
* LCDecoder.cs:
* LCEncoder.cs:
* LCHttpClient.cs:
* ILCOperation.cs:
* LCAddOperation.cs:
* LCSetOperation.cs:
* LCDeleteOperation.cs:
* LCRemoveOperation.cs:
* LCAddUniqueOperation.cs:
* LCDecrementOperation.cs:
* LCCompositionalCondition.cs:
* LCAddRelationOperation.cs:
* LCRemoveRelationOperation.cs:
oneRain 2020-02-25 20:39:49 +08:00
parent c775f612bc
commit 05047115d1
16 changed files with 251 additions and 314 deletions

View File

@ -1,16 +1,15 @@
using NUnit.Framework;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using LeanCloud.Common;
using LeanCloud.Storage;
namespace LeanCloud.Test {
public class ObjectTests {
public class ObjectTest {
[SetUp]
public void SetUp() {
Logger.LogDelegate += Utils.Print;
Utils.InitNorthChina();
LeanCloud.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", "NUKmuRbdAhg1vrb2wexYo1jo", "https://ikggdre2.lc-cn-n1-shared.com");
}
[TearDown]
@ -19,296 +18,130 @@ namespace LeanCloud.Test {
}
[Test]
public async Task Save() {
AVObject obj = AVObject.Create("Foo");
obj["content"] = "hello, world";
obj["list"] = new List<int> { 1, 1, 2, 3, 5, 8 };
obj["dict"] = new Dictionary<string, int> {
{ "hello", 1 },
{ "world", 2 }
public async Task CreateObject() {
LCObject @object = new LCObject("Hello");
@object["intValue"] = 123;
@object["boolValue"] = true;
@object["stringValue"] = "hello, world";
@object["time"] = DateTime.Now;
@object["intList"] = new List<int> { 1, 1, 2, 3, 5, 8 };
@object["stringMap"] = new Dictionary<string, object> {
{ "k1", 111 },
{ "k2", true },
{ "k3", "haha" }
};
await obj.SaveAsync(true);
Assert.NotNull(obj.ObjectId);
Assert.NotNull(obj.CreatedAt);
Assert.NotNull(obj.UpdatedAt);
LCObject nestedObj = new LCObject("World");
nestedObj["content"] = "7788";
@object["objectValue"] = nestedObj;
@object["pointerList"] = new List<object> { new LCObject("World"), nestedObj };
await @object.Save();
TestContext.WriteLine(@object.ClassName);
TestContext.WriteLine(@object.ObjectId);
TestContext.WriteLine(@object.CreatedAt);
TestContext.WriteLine(@object.UpdatedAt);
TestContext.WriteLine(@object["intValue"]);
TestContext.WriteLine(@object["boolValue"]);
TestContext.WriteLine(@object["stringValue"]);
TestContext.WriteLine(@object["objectValue"]);
TestContext.WriteLine(@object["time"]);
Assert.AreEqual(nestedObj, @object["objectValue"]);
TestContext.WriteLine(nestedObj.ClassName);
TestContext.WriteLine(nestedObj.ObjectId);
Assert.NotNull(@object.ObjectId);
Assert.NotNull(@object.ClassName);
Assert.NotNull(@object.CreatedAt);
Assert.NotNull(@object.UpdatedAt);
Assert.AreEqual(@object["intValue"], 123);
Assert.AreEqual(@object["boolValue"], true);
Assert.AreEqual(@object["stringValue"], "hello, world");
Assert.NotNull(nestedObj);
Assert.NotNull(nestedObj.ClassName);
Assert.NotNull(nestedObj.ObjectId);
Assert.NotNull(nestedObj.CreatedAt);
Assert.NotNull(nestedObj.UpdatedAt);
List<object> pointerList = @object["pointerList"] as List<object>;
foreach (object pointerObj in pointerList) {
LCObject pointer = pointerObj as LCObject;
Assert.NotNull(pointer.ObjectId);
}
}
[Test]
public async Task SaveWithOptions() {
AVObject account = AVObject.CreateWithoutData("Account", "5d65fa5330863b008065e476");
account["balance"] = 100;
await account.SaveAsync();
AVQuery<AVObject> query = new AVQuery<AVObject>("Account");
query.WhereGreaterThan("balance", 80);
account["balance"] = 50;
await account.SaveAsync(true, query);
TestContext.Out.WriteLine($"balance: {account["balance"]}");
}
//[Test]
//public async Task SaveWithPointer() {
// AVObject comment = new AVObject("Comment") {
// { "content", "Hello, Comment" }
// };
// AVObject post = new AVObject("Post") {
// { "name", "New Post" },
// { "category", new AVObject("Category") {
// { "name", "new post category" }
// } }
// };
// comment["post"] = post;
// AVObject testPost = new AVObject("Post") {
// { "name", "Test Post" },
// { "category", new AVObject("Category") {
// { "name", "test post category" }
// } }
// };
// comment["test_post"] = testPost;
// await comment.Save();
// TestContext.Out.WriteLine(post);
// TestContext.Out.WriteLine(testPost);
// TestContext.Out.WriteLine(comment);
//}
[Test]
public async Task SaveWithPointer() {
AVObject parent = new AVObject("Parent");
AVObject c1 = new AVObject("C1");
AVObject c2 = new AVObject("C2");
parent["c1"] = c1;
parent["c2"] = c2;
await parent.SaveAsync();
}
[Test]
public async Task SaveWithPointerArray() {
AVObject parent = new AVObject("Parent");
AVObject c1 = new AVObject("C1");
AVObject c2 = new AVObject("C2");
parent["iList"] = new List<int> { 1, 1, 2, 3 };
parent["cList"] = new List<AVObject> { c1, c2 };
parent["cDict"] = new Dictionary<string, AVObject> {
{ "c1", c1 },
{ "c2", c2 }
};
await parent.SaveAsync();
}
[Test]
public async Task SaveBatch() {
List<AVObject> objList = new List<AVObject>();
public async Task SaveAll() {
List<LCObject> list = new List<LCObject>();
for (int i = 0; i < 5; i++) {
AVObject obj = AVObject.Create("Foo");
obj["content"] = "batch object";
objList.Add(obj);
LCObject world = new LCObject("World");
world["content"] = $"word_{i}";
list.Add(world);
}
try {
await objList.SaveAllAsync();
objList.ForEach(obj => {
Assert.NotNull(obj.ObjectId);
});
} catch (Exception e) {
TestContext.Out.WriteLine(e.Message);
}
}
[Test]
public async Task Fetch() {
AVObject obj = AVObject.CreateWithoutData("Todo", "5d5f6039d5de2b006cf29c8f");
await obj.FetchAsync();
Assert.NotNull(obj["title"]);
Assert.NotNull(obj["content"]);
TestContext.Out.WriteLine($"{obj["title"]}, {obj["content"]}");
}
[Test]
public async Task FetchWithKeys() {
AVObject obj = AVObject.CreateWithoutData("Post", "5d3abfa530863b0068e1b326");
await obj.FetchAsync(new List<string> { "pubUser" });
TestContext.Out.WriteLine($"{obj["pubUser"]}");
}
[Test]
public async Task FetchWithIncludes() {
AVObject obj = AVObject.CreateWithoutData("Post", "5d3abfa530863b0068e1b326");
await obj.FetchAsync(includes: new List<string> { "tag" });
AVObject tag = obj["tag"] as AVObject;
TestContext.Out.WriteLine($"{tag["name"]}");
}
[Test]
public async Task FetchAll() {
List<AVObject> objList = new List<AVObject> {
AVObject.CreateWithoutData("Tag", "5d64e5ebc05a8000730340ba"),
AVObject.CreateWithoutData("Tag", "5d64e5eb12215f0073db271c"),
AVObject.CreateWithoutData("Tag", "5d64e57f43e78c0068a14315")
};
await objList.FetchAllAsync();
objList.ForEach(obj => {
await LCObject.SaveAll(list);
foreach (LCObject obj in list) {
Assert.NotNull(obj.ObjectId);
TestContext.Out.WriteLine($"{obj.ObjectId}, {obj["name"]}");
});
}
}
[Test]
public async Task Delete() {
AVObject obj = AVObject.Create("Foo");
obj["content"] = "hello, world";
await obj.SaveAsync();
Assert.NotNull(obj);
await obj.DeleteAsync();
}
[Test]
public async Task DeleteWithCondition() {
AVObject account = new AVObject("Account") {
{ "balance", 100 },
};
account.ACL = new AVACL {
PublicWriteAccess = true,
PublicReadAccess = true
};
await account.SaveAsync();
AVQuery<AVObject> condition = new AVQuery<AVObject>();
condition.WhereGreaterThan("balance", 10);
await account.DeleteAsync(condition);
account = new AVObject("Account") {
{ "name", "acl account" },
{ "balance", 8 },
};
account.ACL = new AVACL {
PublicWriteAccess = true,
PublicReadAccess = true
};
await account.SaveAsync();
condition = new AVQuery<AVObject>();
condition.WhereGreaterThan("balance", 10);
try {
await account.DeleteAsync(condition);
} catch (AVException e) {
Assert.AreEqual(e.Code, AVException.ErrorCode.NoEffectOnUpdatingOrDeleting);
}
LCObject world = new LCObject("World");
await world.Save();
await world.Delete();
}
[Test]
public async Task DeleteAll() {
List<AVObject> objList = new List<AVObject>();
for (int i = 0; i < 5; i++) {
AVObject obj = AVObject.Create("Foo");
obj.ACL = new AVACL {
PublicReadAccess = true,
PublicWriteAccess = i % 2 == 0
};
obj["content"] = "batch object";
objList.Add(obj);
}
await objList.SaveAllAsync();
List<LCObject> list = new List<LCObject> {
new LCObject("World"),
new LCObject("World"),
new LCObject("World"),
new LCObject("World")
};
await LCObject.SaveAll(list);
await LCObject.DeleteAll(list);
}
[Test]
public async Task Fetch() {
LCObject hello = LCObject.CreateWithoutData("Hello", "5e14392743c257006fb769d5");
await hello.Fetch(includes: new List<string> { "objectValue" });
LCObject world = hello["objectValue"] as LCObject;
TestContext.WriteLine(world["content"]);
Assert.AreEqual(world["content"], "7788");
}
[Test]
public async Task SaveWithOption() {
LCObject account = new LCObject("Account");
account["balance"] = 10;
await account.Save();
account["balance"] = 1000;
LCQuery<LCObject> q = new LCQuery<LCObject>("Account");
q.WhereGreaterThan("balance", 100);
try {
await AVObject.DeleteAllAsync(objList);
} catch (AggregateException e) {
foreach (AVException ie in e.InnerExceptions) {
TestContext.Out.WriteLine($"{ie.Code} : {ie.Message}");
}
await account.Save(fetchWhenSave: true, query: q);
} catch(LCException e) {
TestContext.WriteLine($"{e.Code} : {e.Message}");
Assert.AreEqual(e.Code, 305);
}
}
[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 at {Thread.CurrentThread.ManagedThreadId}");
});
}
Thread.Sleep(1000);
}
});
}
public async Task Unset() {
LCObject hello = new LCObject("Hello");
hello["content"] = "hello, world";
await hello.Save();
TestContext.WriteLine(hello["content"]);
Assert.AreEqual(hello["content"], "hello, world");
[Test]
public void SimpleCircleReference() {
AVObject a = new AVObject("A");
AVObject b = new AVObject("B");
a["b"] = b;
b["a"] = a;
Assert.ThrowsAsync<AVException>(async () => await a.SaveAsync());
}
[Test]
public void IndirectCircleReference() {
AVObject a = new AVObject("A");
AVObject b = new AVObject("B");
AVObject c = new AVObject("C");
a["b"] = b;
b["c"] = c;
c["a"] = a;
Assert.ThrowsAsync<AVException>(async () => await a.SaveAsync());
}
[Test]
public void SimpleCollectionPointerCircleReference() {
AVObject a = new AVObject("A");
AVObject b = new AVObject("B");
a["children"] = new List<object> { 1, b };
b["children"] = new Dictionary<string, object> {
{ "c", a }
};
Assert.ThrowsAsync<AVException>(async () => await a.SaveAsync());
}
[Test]
public void IndirectCollectionPointerCircleReference() {
AVObject a = new AVObject("A");
AVObject b = new AVObject("B");
AVObject c = new AVObject("C");
a["children"] = new List<object> { 1, b };
b["children"] = new List<object> { 2, c };
c["children"] = new Dictionary<string, object> {
{ "c", a }
};
Assert.ThrowsAsync<AVException>(async () => await a.SaveAsync());
}
[Test]
public async Task SimpleSavePointerCollection() {
AVObject p = new AVObject("P");
AVObject c1 = new AVObject("C1");
AVObject c2 = new AVObject("C2");
p["cList"] = new List<AVObject> { c1, c2 };
p["cDict"] = new Dictionary<string, object> {
{ "c1", c1 },
{ "c2", c2 }
};
await p.SaveAsync();
Assert.NotNull(p.ObjectId);
Assert.NotNull(p.CreatedAt);
Assert.NotNull(c1.ObjectId);
Assert.NotNull(c2.ObjectId);
}
[Test]
public async Task SaveWithExistedObject() {
AVObject p = new AVObject("P");
AVObject c1 = AVObject.CreateWithoutData("C1", "5dea05578a84ab00680b7ae5");
AVObject c2 = new AVObject("C2");
p["c"] = c1;
c1["c"] = c2;
await p.SaveAsync();
Assert.NotNull(p.ObjectId);
Assert.NotNull(p.CreatedAt);
hello.Unset("content");
await hello.Save();
TestContext.WriteLine(hello["content"]);
Assert.IsNull(hello["content"]);
}
}
}

View File

@ -56,8 +56,7 @@ namespace LeanCloud.Storage.Internal.Codec {
string className = dict["className"].ToString();
LCObject obj = LCObject.Create(className);
LCObjectData objectData = LCObjectData.Decode(dict as Dictionary<string, object>);
// TODO merge
obj.Merge(objectData);
return obj;
}

View File

@ -58,10 +58,10 @@ namespace LeanCloud.Storage.Internal.Codec {
static object EncodeDictionary(IDictionary dict) {
Dictionary<string, object> d = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> kv in dict) {
string key = kv.Key;
object value = kv.Value;
d[key] = value;
foreach (DictionaryEntry entry in dict) {
string key = entry.Key.ToString();
object value = entry.Value;
d[key] = Encode(value);
}
return d;
}
@ -75,11 +75,11 @@ namespace LeanCloud.Storage.Internal.Codec {
}
static object EncodeOperation(ILCOperation operation) {
return null;
return operation.Encode();
}
static object EncodeQueryCondition(ILCQueryCondition cond) {
return null;
return cond.Encode();
}
static object EncodeACL(LCACL acl) {

View File

@ -38,7 +38,7 @@ namespace LeanCloud.Storage.Internal.Http {
client.DefaultRequestHeaders.Add("X-LC-Key", appKey);
}
internal async Task<Dictionary<string, object>> Get(string path,
internal async Task<T> Get<T>(string path,
Dictionary<string, object> headers = null,
Dictionary<string, object> queryParams = null) {
@ -53,6 +53,11 @@ namespace LeanCloud.Storage.Internal.Http {
RequestUri = new Uri(url),
Method = HttpMethod.Get
};
if (headers != null) {
foreach (KeyValuePair<string, object> kv in headers) {
request.Headers.Add(kv.Key, kv.Value.ToString());
}
}
HttpUtils.PrintRequest(client, request);
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
request.Dispose();
@ -63,7 +68,7 @@ namespace LeanCloud.Storage.Internal.Http {
HttpStatusCode statusCode = response.StatusCode;
if (response.IsSuccessStatusCode) {
Dictionary<string, object> ret = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());
T ret = JsonConvert.DeserializeObject<T>(resultString, new LeanCloudJsonConverter());
return ret;
}
int code = (int)statusCode;
@ -109,7 +114,7 @@ namespace LeanCloud.Storage.Internal.Http {
try {
// 尝试获取 LeanCloud 返回错误信息
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());
code = (int)error["code"];
code = (int)(long)error["code"];
message = error["error"].ToString();
} catch (Exception e) {
Logger.Error(e.Message);
@ -118,13 +123,19 @@ namespace LeanCloud.Storage.Internal.Http {
}
}
internal async Task<Dictionary<string, object>> Put(string path,
internal async Task<T> Put<T>(string path,
Dictionary<string, object> headers = null,
Dictionary<string, object> data = null,
Dictionary<string, object> queryParams = null) {
string url = $"{server}/{apiVersion}/{path}";
if (queryParams != null) {
IEnumerable<string> queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}");
string queries = string.Join("&", queryPairs);
url = $"{url}?{queries}";
}
string content = (data != null) ? JsonConvert.SerializeObject(data) : null;
HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri($"{server}/{apiVersion}/{path}"),
RequestUri = new Uri(url),
Method = HttpMethod.Put,
Content = new StringContent(content)
};
@ -139,11 +150,43 @@ namespace LeanCloud.Storage.Internal.Http {
HttpStatusCode statusCode = response.StatusCode;
if (response.IsSuccessStatusCode) {
Dictionary<string, object> ret = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());
T ret = JsonConvert.DeserializeObject<T>(resultString, new LeanCloudJsonConverter());
return ret;
}
int code = (int)statusCode;
string message = resultString;
try {
// 尝试获取 LeanCloud 返回错误信息
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());
code = (int)(long)error["code"];
message = error["error"].ToString();
} catch (Exception e) {
Logger.Error(e.Message);
} finally {
throw new LCException(code, message);
}
}
internal async Task Delete(string path) {
HttpRequestMessage request = new HttpRequestMessage {
RequestUri = new Uri($"{server}/{apiVersion}/{path}"),
Method = HttpMethod.Delete
};
HttpUtils.PrintRequest(client, request);
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
request.Dispose();
string resultString = await response.Content.ReadAsStringAsync();
response.Dispose();
HttpUtils.PrintResponse(response, resultString);
HttpStatusCode statusCode = response.StatusCode;
if (response.IsSuccessStatusCode) {
Dictionary<string, object> ret = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());
return;
}
int code = (int)statusCode;
string message = resultString;
try {
// 尝试获取 LeanCloud 返回错误信息
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());

View File

@ -5,7 +5,7 @@ namespace LeanCloud.Storage.Internal.Operation {
internal interface ILCOperation {
ILCOperation MergeWithPrevious(ILCOperation previousOp);
Dictionary<string, object> Encode();
object Encode();
object Apply(object oldValue, string key);

View File

@ -26,7 +26,7 @@ namespace LeanCloud.Storage.Internal.Operation {
throw new ArgumentException("Operation is invalid after previous operation.");
}
Dictionary<string, object> ILCOperation.Encode() {
object ILCOperation.Encode() {
return new Dictionary<string, object> {
{ "op", "Add" },
{ "objects", LCEncoder.Encode(valueList) }

View File

@ -23,7 +23,7 @@ namespace LeanCloud.Storage.Internal.Operation {
throw new ArgumentException("Operation is invalid after previous operation.");
}
public Dictionary<string, object> Encode() {
public object Encode() {
return new Dictionary<string, object> {
{ "__op", "AddRelation" },
{ "objects", LCEncoder.Encode(valueList) }

View File

@ -23,7 +23,7 @@ namespace LeanCloud.Storage.Internal.Operation {
throw new ArgumentException("Operation is invalid after previous operation.");
}
public Dictionary<string, object> Encode() {
public object Encode() {
return new Dictionary<string, object> {
{ "__op", "AddUnique" },
{ "objects", LCEncoder.Encode(values.ToList()) }

View File

@ -13,7 +13,7 @@ namespace LeanCloud.Storage.Internal.Operation {
throw new NotImplementedException();
}
public Dictionary<string, object> Encode() {
public object Encode() {
throw new NotImplementedException();
}

View File

@ -11,7 +11,7 @@ namespace LeanCloud.Storage.Internal.Operation {
return this;
}
public Dictionary<string, object> Encode() {
public object Encode() {
return new Dictionary<string, object> {
{ "__op", "Delete" }
};

View File

@ -24,7 +24,7 @@ namespace LeanCloud.Storage.Internal.Operation {
throw new ArgumentException("Operation is invalid after previous operation.");
}
public Dictionary<string, object> Encode() {
public object Encode() {
return new Dictionary<string, object> {
{ "__op", "Remove" },
{ "objects", LCEncoder.Encode(valueList) }

View File

@ -22,7 +22,7 @@ namespace LeanCloud.Storage.Internal.Operation {
throw new ArgumentException("Operation is invalid after previous operation.");
}
public Dictionary<string, object> Encode() {
public object Encode() {
return new Dictionary<string, object> {
{ "__op", "RemoveRelation" },
{ "objects", LCEncoder.Encode(valueList) }

View File

@ -15,8 +15,8 @@ namespace LeanCloud.Storage.Internal.Operation {
return this;
}
public Dictionary<string, object> Encode() {
return LCEncoder.Encode(value) as Dictionary<string, object>;
public object Encode() {
return LCEncoder.Encode(value);
}
public object Apply(object oldValue, string key) {

View File

@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
using LeanCloud.Storage.Internal.Codec;
namespace LeanCloud.Storage.Internal.Query {
@ -189,9 +190,7 @@ namespace LeanCloud.Storage.Internal.Query {
if (conditionList == null || conditionList.Count == 0) {
return null;
}
// TODO
return null;
return JsonConvert.SerializeObject(Encode());
}
}
}

View File

@ -87,7 +87,7 @@ namespace LeanCloud.Storage {
LCObject obj = new LCObject(className);
obj.data.ObjectId = objectId;
obj.isNew = false;
return null;
return obj;
}
internal static LCObject Create(string className) {
@ -106,12 +106,14 @@ namespace LeanCloud.Storage {
public object this[string key] {
get {
object value = estimatedData[key];
if (value is LCRelation<LCObject> relation) {
relation.Key = key;
relation.Parent = this;
if (estimatedData.TryGetValue(key, out object value)) {
if (value is LCRelation<LCObject> relation) {
relation.Key = key;
relation.Parent = this;
}
return value;
}
return value;
return null;
}
set {
if (string.IsNullOrEmpty(key)) {
@ -161,11 +163,13 @@ namespace LeanCloud.Storage {
List<Dictionary<string, object>> results = await LeanCloud.HttpClient.Post<List<Dictionary<string, object>>>("batch", data: data);
List<LCObjectData> resultList = results.Select(item => {
if (item.TryGetValue("error", out object message)) {
int code = (int)item["code"];
if (item.TryGetValue("error", out object error)) {
Dictionary<string, object> err = error as Dictionary<string, object>;
int code = (int)err["code"];
string message = (string)err["error"];
throw new LCException(code, message as string);
}
return LCObjectData.Decode(item);
return LCObjectData.Decode(item["success"] as IDictionary);
}).ToList();
for (int i = 0; i < dirtyObjects.Count; i++) {
@ -196,12 +200,68 @@ namespace LeanCloud.Storage {
}
Dictionary<string, object> response = ObjectId == null ?
await LeanCloud.HttpClient.Post<Dictionary<string, object>>(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams) :
await LeanCloud.HttpClient.Put(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams);
await LeanCloud.HttpClient.Put<Dictionary<string, object>>(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams);
LCObjectData data = LCObjectData.Decode(response);
Merge(data);
return this;
}
public static async Task<List<LCObject>> SaveAll(List<LCObject> objectList) {
if (objectList == null) {
throw new ArgumentNullException(nameof(objectList));
}
foreach (LCObject obj in objectList) {
if (LCBatch.HasCircleReference(obj, new HashSet<LCObject>())) {
throw new ArgumentException("Found a circle dependency when save.");
}
}
Stack<LCBatch> batches = LCBatch.BatchObjects(objectList, true);
await SaveBatches(batches);
return objectList;
}
public async Task Delete() {
if (ObjectId == null) {
return;
}
string path = $"classes/{ClassName}/{ObjectId}";
await LeanCloud.HttpClient.Delete(path);
}
public static async Task DeleteAll(List<LCObject> objectList) {
if (objectList == null || objectList.Count == 0) {
throw new ArgumentNullException(nameof(objectList));
}
IEnumerable<LCObject> objects = objectList.Where(item => item.ObjectId != null);
HashSet<LCObject> objectSet = new HashSet<LCObject>(objects);
List<Dictionary<string, object>> requestList = objectSet.Select(item => {
string path = $"/{LeanCloud.APIVersion}/classes/{item.ClassName}/{item.ObjectId}";
return new Dictionary<string, object> {
{ "path", path },
{ "method", "DELETE" }
};
}).ToList();
Dictionary<string, object> data = new Dictionary<string, object> {
{ "requests", LCEncoder.Encode(requestList) }
};
await LeanCloud.HttpClient.Post<List<object>>("batch", data: data);
}
public async Task<LCObject> Fetch(IEnumerable<string> keys = null, IEnumerable<string> includes = null) {
Dictionary<string, object> queryParams = new Dictionary<string, object>();
if (keys != null) {
queryParams["keys"] = string.Join(",", keys);
}
if (includes != null) {
queryParams["include"] = string.Join(",", includes);
}
string path = $"classes/{ClassName}/{ObjectId}";
Dictionary<string, object> response = await LeanCloud.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams);
LCObjectData objectData = LCObjectData.Decode(response);
Merge(objectData);
return this;
}
public static void RegisterSubclass(string className, Type type, Func<LCObject> constructor) {
LCSubclassInfo subclassInfo = new LCSubclassInfo(className, type, constructor);
subclassNameDict[className] = subclassInfo;
@ -217,8 +277,11 @@ namespace LeanCloud.Storage {
if (op is LCDeleteOperation) {
estimatedData.Remove(key);
} else {
object oldValue = estimatedData[key];
estimatedData[key] = op.Apply(oldValue, key);
if (estimatedData.TryGetValue(key, out object oldValue)) {
estimatedData[key] = op.Apply(oldValue, key);
} else {
estimatedData[key] = op.Apply(null, key);
}
}
}

View File

@ -274,7 +274,7 @@ namespace LeanCloud.Storage {
Dictionary<string, object> parameters = BuildParams();
parameters["limit"] = 0;
parameters["count"] = 1;
Dictionary<string, object> ret = await LeanCloud.HttpClient.Get(path, queryParams: parameters);
Dictionary<string, object> ret = await LeanCloud.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters);
return (int)ret["count"];
}
@ -297,7 +297,7 @@ namespace LeanCloud.Storage {
public async Task<List<T>> Find() {
string path = $"classes/{ClassName}";
Dictionary<string, object> parameters = BuildParams();
Dictionary<string, object> response = await LeanCloud.HttpClient.Get(path, queryParams: parameters);
Dictionary<string, object> response = await LeanCloud.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters);
List<object> results = response["results"] as List<object>;
List<T> list = new List<T>();
foreach (object item in results) {