csharp-sdk-upm/Storage/Source/Public/AVQuery.cs

361 lines
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using LeanCloud.Storage.Internal;
namespace LeanCloud
{
/// <summary>
/// The AVQuery class defines a query that is used to fetch AVObjects. The
/// most common use case is finding all objects that match a query through the
/// <see cref="FindAsync()"/> method.
/// </summary>
/// <example>
/// This sample code fetches all objects of
/// class <c>"MyClass"</c>:
///
/// <code>
/// AVQuery query = new AVQuery("MyClass");
/// IEnumerable&lt;AVObject&gt; result = await query.FindAsync();
/// </code>
///
/// A AVQuery can also be used to retrieve a single object whose id is known,
/// through the <see cref="GetAsync(string)"/> method. For example, this sample code
/// fetches an object of class <c>"MyClass"</c> and id <c>myId</c>.
///
/// <code>
/// AVQuery query = new AVQuery("MyClass");
/// AVObject result = await query.GetAsync(myId);
/// </code>
///
/// A AVQuery can also be used to count the number of objects that match the
/// query without retrieving all of those objects. For example, this sample code
/// counts the number of objects of the class <c>"MyClass"</c>.
///
/// <code>
/// AVQuery query = new AVQuery("MyClass");
/// int count = await query.CountAsync();
/// </code>
/// </example>
public class AVQuery<T> : AVQueryPair<AVQuery<T>, T>, IAVQuery
where T : AVObject
{
internal static IAVQueryController QueryController
{
get
{
return AVPlugins.Instance.QueryController;
}
}
internal static IObjectSubclassingController SubclassingController
{
get
{
return AVPlugins.Instance.SubclassingController;
}
}
/// <summary>
/// 调试时可以用来查看最终的发送的查询语句
/// </summary>
private string JsonString
{
get
{
return AVClient.SerializeJsonString(this.BuildParameters(true));
}
}
/// <summary>
/// Private constructor for composition of queries. A Source query is required,
/// but the remaining values can be null if they won't be changed in this
/// composition.
/// </summary>
private AVQuery(AVQuery<T> source,
IDictionary<string, object> where = null,
IEnumerable<string> replacementOrderBy = null,
IEnumerable<string> thenBy = null,
int? skip = null,
int? limit = null,
IEnumerable<string> includes = null,
IEnumerable<string> selectedKeys = null,
string redirectClassNameForKey = null)
: base(source, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey)
{
}
//internal override AVQuery<T> CreateInstance(
// AVQuery<T> source,
// IDictionary<string, object> where = null,
// IEnumerable<string> replacementOrderBy = null,
// IEnumerable<string> thenBy = null,
// int? skip = null,
// int? limit = null,
// IEnumerable<string> includes = null,
// IEnumerable<string> selectedKeys = null,
// String redirectClassNameForKey = null)
//{
// return new AVQuery<T>(this, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey);
//}
public override AVQuery<T> CreateInstance(
IDictionary<string, object> where = null,
IEnumerable<string> replacementOrderBy = null,
IEnumerable<string> thenBy = null,
int? skip = null,
int? limit = null,
IEnumerable<string> includes = null,
IEnumerable<string> selectedKeys = null,
string redirectClassNameForKey = null)
{
return new AVQuery<T>(this, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey);
}
/// <summary>
/// Constructs a query based upon the AVObject subclass used as the generic parameter for the AVQuery.
/// </summary>
public AVQuery()
: this(SubclassingController.GetClassName(typeof(T)))
{
}
/// <summary>
/// Constructs a query. A default query with no further parameters will retrieve
/// all <see cref="AVObject"/>s of the provided class.
/// </summary>
/// <param name="className">The name of the class to retrieve AVObjects for.</param>
public AVQuery(string className)
: base(className)
{
}
/// <summary>
/// Constructs a query that is the or of the given queries.
/// </summary>
/// <param name="queries">The list of AVQueries to 'or' together.</param>
/// <returns>A AVQquery that is the 'or' of the passed in queries.</returns>
public static AVQuery<T> Or(IEnumerable<AVQuery<T>> queries)
{
string className = null;
var orValue = new List<IDictionary<string, object>>();
// We need to cast it to non-generic IEnumerable because of AOT-limitation
var nonGenericQueries = (IEnumerable)queries;
foreach (var obj in nonGenericQueries)
{
var q = (AVQuery<T>)obj;
if (className != null && q.className != className)
{
throw new ArgumentException(
"All of the queries in an or query must be on the same class.");
}
className = q.className;
var parameters = q.BuildParameters();
if (parameters.Count == 0)
{
continue;
}
object where;
if (!parameters.TryGetValue("where", out where) || parameters.Count > 1)
{
throw new ArgumentException(
"None of the queries in an or query can have non-filtering clauses");
}
orValue.Add(where as IDictionary<string, object>);
}
return new AVQuery<T>(new AVQuery<T>(className),
where: new Dictionary<string, object> {
{ "$or", orValue}
});
}
/// <summary>
/// Retrieves a list of AVObjects that satisfy this query from LeanCloud.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The list of AVObjects that match this query.</returns>
public override Task<IEnumerable<T>> FindAsync(CancellationToken cancellationToken)
{
return AVUser.GetCurrentUserAsync().OnSuccess(t =>
{
return QueryController.FindAsync<T>(this, t.Result, cancellationToken);
}).Unwrap().OnSuccess(t =>
{
IEnumerable<IObjectState> states = t.Result;
return (from state in states
select AVObject.FromState<T>(state, ClassName));
});
}
/// <summary>
/// Retrieves at most one AVObject that satisfies this query.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A single AVObject that satisfies this query, or else null.</returns>
public override Task<T> FirstOrDefaultAsync(CancellationToken cancellationToken)
{
return AVUser.GetCurrentUserAsync().OnSuccess(t =>
{
return QueryController.FirstAsync<T>(this, t.Result, cancellationToken);
}).Unwrap().OnSuccess(t =>
{
IObjectState state = t.Result;
return state == null ? default(T) : AVObject.FromState<T>(state, ClassName);
});
}
/// <summary>
/// Retrieves at most one AVObject that satisfies this query.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A single AVObject that satisfies this query.</returns>
/// <exception cref="AVException">If no results match the query.</exception>
public override Task<T> FirstAsync(CancellationToken cancellationToken)
{
return FirstOrDefaultAsync(cancellationToken).OnSuccess(t =>
{
if (t.Result == null)
{
throw new AVException(AVException.ErrorCode.ObjectNotFound,
"No results matched the query.");
}
return t.Result;
});
}
/// <summary>
/// Counts the number of objects that match this query.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The number of objects that match this query.</returns>
public override Task<int> CountAsync(CancellationToken cancellationToken)
{
return AVUser.GetCurrentUserAsync().OnSuccess(t =>
{
return QueryController.CountAsync<T>(this, t.Result, cancellationToken);
}).Unwrap();
}
/// <summary>
/// Constructs a AVObject whose id is already known by fetching data
/// from the server.
/// </summary>
/// <param name="objectId">ObjectId of the AVObject to fetch.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The AVObject for the given objectId.</returns>
public override Task<T> GetAsync(string objectId, CancellationToken cancellationToken)
{
AVQuery<T> singleItemQuery = new AVQuery<T>(className)
.WhereEqualTo("objectId", objectId);
singleItemQuery = new AVQuery<T>(singleItemQuery, includes: this.includes, selectedKeys: this.selectedKeys, limit: 1);
return singleItemQuery.FindAsync(cancellationToken).OnSuccess(t =>
{
var result = t.Result.FirstOrDefault();
if (result == null)
{
throw new AVException(AVException.ErrorCode.ObjectNotFound,
"Object with the given objectId not found.");
}
return result;
});
}
#region CQL
/// <summary>
/// 执行 CQL 查询
/// </summary>
/// <param name="cql">CQL 语句</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>返回符合条件的对象集合</returns>
public static Task<IEnumerable<T>> DoCloudQueryAsync(string cql, CancellationToken cancellationToken)
{
var queryString = string.Format("cloudQuery?cql={0}", Uri.EscapeDataString(cql));
return rebuildObjectFromCloudQueryResult(queryString);
}
/// <summary>
/// 执行 CQL 查询
/// </summary>
/// <param name="cql"></param>
/// <returns></returns>
public static Task<IEnumerable<T>> DoCloudQueryAsync(string cql)
{
return DoCloudQueryAsync(cql, CancellationToken.None);
}
/// <summary>
/// 执行 CQL 查询
/// </summary>
/// <param name="cqlTeamplate">带有占位符的模板 cql 语句</param>
/// <param name="pvalues">占位符对应的参数数组</param>
/// <returns></returns>
public static Task<IEnumerable<T>> DoCloudQueryAsync(string cqlTeamplate, params object[] pvalues)
{
string queryStringTemplate = "cloudQuery?cql={0}&pvalues={1}";
string pSrting = Json.Encode(pvalues);
string queryString = string.Format(queryStringTemplate, Uri.EscapeDataString(cqlTeamplate), Uri.EscapeDataString(pSrting));
return rebuildObjectFromCloudQueryResult(queryString);
}
internal static Task<IEnumerable<T>> rebuildObjectFromCloudQueryResult(string queryString)
{
var command = new AVCommand(queryString,
method: "GET",
sessionToken: AVUser.CurrentSessionToken,
data: null);
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: CancellationToken.None).OnSuccess(t =>
{
var items = t.Result.Item2["results"] as IList<object>;
var className = t.Result.Item2["className"].ToString();
IEnumerable<IObjectState> states = (from item in items
select AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance));
return (from state in states
select AVObject.FromState<T>(state, className));
});
}
#endregion
/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c></returns>
public override bool Equals(object obj)
{
if (obj == null || !(obj is AVQuery<T>))
{
return false;
}
var other = obj as AVQuery<T>;
return Object.Equals(this.className, other.ClassName) &&
this.where.CollectionsEqual(other.where) &&
this.orderBy.CollectionsEqual(other.orderBy) &&
this.includes.CollectionsEqual(other.includes) &&
this.selectedKeys.CollectionsEqual(other.selectedKeys) &&
Object.Equals(this.skip, other.skip) &&
Object.Equals(this.limit, other.limit);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}