* ObjectControllerTests.cs: test: 测试批量操作

* AVObject.cs:
* AVFileExtensions.cs:
* MutableObjectState.cs:
* AVObjectController.cs:
* ObjectSubclassInfo.cs:
* ObjectSubclassingController.cs:
oneRain 2019-09-02 14:01:28 +08:00
parent 7f29ee2cbf
commit 7980d47168
7 changed files with 98 additions and 106 deletions

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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;