361 lines
14 KiB
C#
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<AVObject> 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();
|
|
}
|
|
}
|
|
}
|