* ObjectControllerTests.cs: test: 测试批量操作
* AVObject.cs: * AVFileExtensions.cs: * MutableObjectState.cs: * AVObjectController.cs: * ObjectSubclassInfo.cs: * ObjectSubclassingController.cs:
parent
7f29ee2cbf
commit
7980d47168
|
@ -13,11 +13,9 @@ namespace LeanCloudTests {
|
|||
|
||||
[Test]
|
||||
public async Task Save() {
|
||||
TestContext.Out.WriteLine($"before at {Thread.CurrentThread.ManagedThreadId}");
|
||||
AVObject obj = AVObject.Create("Foo");
|
||||
obj["content"] = "hello, world";
|
||||
await obj.SaveAsync();
|
||||
TestContext.Out.WriteLine($"{obj.ObjectId} saved at {Thread.CurrentThread.ManagedThreadId}");
|
||||
Assert.NotNull(obj.ObjectId);
|
||||
Assert.NotNull(obj.CreatedAt);
|
||||
Assert.NotNull(obj.UpdatedAt);
|
||||
|
@ -26,6 +24,8 @@ namespace LeanCloudTests {
|
|||
[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;
|
||||
|
@ -33,6 +33,20 @@ namespace LeanCloudTests {
|
|||
TestContext.Out.WriteLine($"balance: {account["balance"]}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveBatch() {
|
||||
List<AVObject> objList = new List<AVObject>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
AVObject obj = AVObject.Create("Foo");
|
||||
obj["content"] = "batch object";
|
||||
objList.Add(obj);
|
||||
}
|
||||
await objList.SaveAllAsync();
|
||||
objList.ForEach(obj => {
|
||||
Assert.NotNull(obj.ObjectId);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Fetch() {
|
||||
AVObject obj = AVObject.CreateWithoutData("Todo", "5d5f6039d5de2b006cf29c8f");
|
||||
|
@ -56,5 +70,40 @@ namespace LeanCloudTests {
|
|||
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 => {
|
||||
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 DeleteAll() {
|
||||
List<AVObject> objList = new List<AVObject>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
AVObject obj = AVObject.Create("Foo");
|
||||
obj["content"] = "batch object";
|
||||
objList.Add(obj);
|
||||
}
|
||||
await objList.SaveAllAsync();
|
||||
await AVObject.DeleteAllAsync(objList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
Path = $"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}?{AVClient.BuildQueryString(queryString)}",
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken).OnSuccess(t => {
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => {
|
||||
return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
});
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
string encode = AVClient.BuildQueryString(args);
|
||||
command.Path = $"{command.Path}?{encode}";
|
||||
}
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken).OnSuccess(t => {
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => {
|
||||
var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
serverState = serverState.MutatedClone(mutableClone => {
|
||||
mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created;
|
||||
|
@ -82,7 +82,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
Path = $"classes/{state.ClassName}/{state.ObjectId}",
|
||||
Method = HttpMethod.Delete
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
|
||||
}
|
||||
|
||||
public IList<Task> DeleteAllAsync(IList<IObjectState> states,
|
||||
|
@ -131,8 +131,8 @@ namespace LeanCloud.Storage.Internal {
|
|||
|
||||
var encodedRequests = requests.Select(r => {
|
||||
var results = new Dictionary<string, object> {
|
||||
{ "method", r.Method },
|
||||
{ "path", r.Path },
|
||||
{ "method", r.Method.Method },
|
||||
{ "path", $"/{AVClient.APIVersion}/{r.Path}" },
|
||||
};
|
||||
|
||||
if (r.Content != null) {
|
||||
|
@ -147,7 +147,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
{ "requests", encodedRequests }
|
||||
}
|
||||
};
|
||||
AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken).ContinueWith(t => {
|
||||
AVPlugins.Instance.CommandRunner.RunCommandAsync<IList<object>>(command, cancellationToken).ContinueWith(t => {
|
||||
if (t.IsFaulted || t.IsCanceled) {
|
||||
foreach (var tcs in tcss) {
|
||||
if (t.IsFaulted) {
|
||||
|
@ -159,7 +159,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
return;
|
||||
}
|
||||
|
||||
var resultsArray = Conversion.As<IList<object>>(t.Result.Item2["results"]);
|
||||
var resultsArray = t.Result.Item2;
|
||||
int resultLength = resultsArray.Count;
|
||||
if (resultLength != batchSize) {
|
||||
foreach (var tcs in tcss) {
|
||||
|
|
|
@ -2,10 +2,8 @@
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class MutableObjectState : IObjectState
|
||||
{
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class MutableObjectState : IObjectState {
|
||||
public bool IsNew { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string ObjectId { get; set; }
|
||||
|
@ -14,84 +12,65 @@ namespace LeanCloud.Storage.Internal
|
|||
|
||||
// Initialize serverData to avoid further null checking.
|
||||
private IDictionary<string, object> serverData = new Dictionary<string, object>();
|
||||
public IDictionary<string, object> ServerData
|
||||
{
|
||||
get
|
||||
{
|
||||
public IDictionary<string, object> ServerData {
|
||||
get {
|
||||
return serverData;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
set {
|
||||
serverData = value;
|
||||
}
|
||||
}
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
public object this[string key] {
|
||||
get {
|
||||
return ServerData[key];
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
public bool ContainsKey(string key) {
|
||||
return ServerData.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void Apply(IDictionary<string, IAVFieldOperation> operationSet)
|
||||
{
|
||||
public void Apply(IDictionary<string, IAVFieldOperation> operationSet) {
|
||||
// Apply operationSet
|
||||
foreach (var pair in operationSet)
|
||||
{
|
||||
foreach (var pair in operationSet) {
|
||||
object oldValue;
|
||||
ServerData.TryGetValue(pair.Key, out oldValue);
|
||||
var newValue = pair.Value.Apply(oldValue, pair.Key);
|
||||
if (newValue != AVDeleteOperation.DeleteToken)
|
||||
{
|
||||
if (newValue != AVDeleteOperation.DeleteToken) {
|
||||
ServerData[pair.Key] = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ServerData.Remove(pair.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Apply(IObjectState other)
|
||||
{
|
||||
public void Apply(IObjectState other) {
|
||||
IsNew = other.IsNew;
|
||||
if (other.ObjectId != null)
|
||||
{
|
||||
if (other.ObjectId != null) {
|
||||
ObjectId = other.ObjectId;
|
||||
}
|
||||
if (other.UpdatedAt != null)
|
||||
{
|
||||
if (other.UpdatedAt != null) {
|
||||
UpdatedAt = other.UpdatedAt;
|
||||
}
|
||||
if (other.CreatedAt != null)
|
||||
{
|
||||
if (other.CreatedAt != null) {
|
||||
CreatedAt = other.CreatedAt;
|
||||
}
|
||||
|
||||
foreach (var pair in other)
|
||||
{
|
||||
foreach (var pair in other) {
|
||||
ServerData[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public IObjectState MutatedClone(Action<MutableObjectState> func)
|
||||
{
|
||||
public IObjectState MutatedClone(Action<MutableObjectState> func) {
|
||||
var clone = MutableClone();
|
||||
func(clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
protected virtual MutableObjectState MutableClone()
|
||||
{
|
||||
return new MutableObjectState
|
||||
{
|
||||
protected virtual MutableObjectState MutableClone() {
|
||||
return new MutableObjectState {
|
||||
IsNew = IsNew,
|
||||
ClassName = ClassName,
|
||||
ObjectId = ObjectId,
|
||||
|
@ -101,13 +80,11 @@ namespace LeanCloud.Storage.Internal
|
|||
};
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
||||
{
|
||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() {
|
||||
return ServerData.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return ((IEnumerable<KeyValuePair<string, object>>)this).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
internal class ObjectSubclassInfo
|
||||
{
|
||||
public ObjectSubclassInfo(Type type, ConstructorInfo constructor)
|
||||
{
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
internal class ObjectSubclassInfo {
|
||||
public ObjectSubclassInfo(Type type, ConstructorInfo constructor) {
|
||||
TypeInfo = type.GetTypeInfo();
|
||||
ClassName = GetClassName(TypeInfo);
|
||||
Constructor = constructor;
|
||||
|
@ -28,15 +21,13 @@ namespace LeanCloud.Storage.Internal
|
|||
public IDictionary<string, string> PropertyMappings { get; private set; }
|
||||
private ConstructorInfo Constructor { get; set; }
|
||||
|
||||
public AVObject Instantiate()
|
||||
{
|
||||
public AVObject Instantiate() {
|
||||
return (AVObject)Constructor.Invoke(null);
|
||||
}
|
||||
|
||||
internal static string GetClassName(TypeInfo type)
|
||||
{
|
||||
internal static string GetClassName(TypeInfo type) {
|
||||
var attribute = type.GetCustomAttribute<AVClassNameAttribute>();
|
||||
return attribute != null ? attribute.ClassName : null;
|
||||
return attribute?.ClassName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
|
||||
private readonly ReaderWriterLockSlim mutex;
|
||||
private readonly IDictionary<string, ObjectSubclassInfo> registeredSubclasses;
|
||||
private Dictionary<string, Action> registerActions;
|
||||
private readonly Dictionary<string, Action> registerActions;
|
||||
|
||||
public ObjectSubclassingController() {
|
||||
mutex = new ReaderWriterLockSlim();
|
||||
|
@ -38,10 +38,8 @@ namespace LeanCloud.Storage.Internal {
|
|||
}
|
||||
|
||||
public bool IsTypeValid(string className, Type type) {
|
||||
ObjectSubclassInfo subclassInfo = null;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out subclassInfo);
|
||||
registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo subclassInfo);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return subclassInfo == null
|
||||
|
@ -63,12 +61,12 @@ namespace LeanCloud.Storage.Internal {
|
|||
// TOCTTOU bug.
|
||||
mutex.EnterWriteLock();
|
||||
|
||||
ObjectSubclassInfo previousInfo = null;
|
||||
if (registeredSubclasses.TryGetValue(className, out previousInfo)) {
|
||||
if (registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo previousInfo)) {
|
||||
if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) {
|
||||
// Previous subclass is more specific or equal to the current type, do nothing.
|
||||
return;
|
||||
} else if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) {
|
||||
}
|
||||
if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) {
|
||||
// Previous subclass is parent of new child, fallthrough and actually register
|
||||
// this class.
|
||||
/* Do nothing */
|
||||
|
@ -91,10 +89,9 @@ namespace LeanCloud.Storage.Internal {
|
|||
mutex.ExitWriteLock();
|
||||
}
|
||||
|
||||
Action toPerform;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registerActions.TryGetValue(className, out toPerform);
|
||||
registerActions.TryGetValue(className, out Action toPerform);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
toPerform?.Invoke();
|
||||
|
@ -113,10 +110,9 @@ namespace LeanCloud.Storage.Internal {
|
|||
}
|
||||
|
||||
public AVObject Instantiate(string className) {
|
||||
ObjectSubclassInfo info = null;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out info);
|
||||
registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return info != null
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVFileExtensions {
|
||||
public static AVFile Create(string name, Uri uri, string mimeType = null) {
|
||||
return new AVFile(name, uri, mimeType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -192,7 +192,7 @@ namespace LeanCloud {
|
|||
|
||||
#endregion
|
||||
|
||||
public static IDictionary<String, String> GetPropertyMappings(string className) {
|
||||
public static IDictionary<string, string> GetPropertyMappings(string className) {
|
||||
return SubclassingController.GetPropertyMappings(className);
|
||||
}
|
||||
|
||||
|
@ -246,7 +246,7 @@ string propertyName
|
|||
string propertyName
|
||||
#endif
|
||||
) {
|
||||
return GetProperty<T>(default(T), propertyName);
|
||||
return GetProperty<T>(default, propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -263,8 +263,7 @@ string propertyName
|
|||
string propertyName
|
||||
#endif
|
||||
) {
|
||||
T result;
|
||||
if (TryGetValue<T>(GetFieldForPropertyName(ClassName, propertyName), out result)) {
|
||||
if (TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out T result)) {
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
|
|
Loading…
Reference in New Issue