检测ClientObject属性是否已经被检索/初始化的正确方法

15

如果你正在使用SharePoint的客户端对象模型并访问尚未初始化或已被检索的属性,则会发生错误

Context.Load(property); 
Context.ExecuteQuery();

举例来说,你可能会遇到以下异常:

Microsoft.SharePoint.Client.PropertyOrFieldNotInitializedException

或者

该集合未初始化。它还没有被请求或请求尚未执行。

异常。

有没有一种适当的方式可以在访问这些属性之前检查它们是否已经初始化/检索?而不是采用Try/Catch方法。我不喜欢那个方法。

我想在抛出异常之前检查并处理它。

我已经查看了IsObjectPropertyInstantiatedIsPropertyAvailable方法,但它们并没有真正帮助到我。IsPropertyAvaiable仅检查标量属性,并且不能对例如Web.Lists进行检查,而IsObjectPropertyInstantiatedWeb.Lists返回true,即使Web.Lists未初始化。

4个回答

29
我认为你的问题在某种程度上已经包含了正确的答案。
为了确定客户端对象属性是否已加载,可以使用以下方法:

测试

测试用例1:仅加载标量属性

ctx.Load(ctx.Web, w => w.Title);
ctx.ExecuteQuery();
//Results:
ctx.Web.IsObjectPropertyInstantiated("Lists")  False
ctx.Web.IsPropertyAvailable("Title")    True

测试用例2:仅加载复合属性

ctx.Load(ctx.Web, w => w.Lists);
ctx.ExecuteQuery();
//Results:
ctx.Web.IsObjectPropertyInstantiated("Lists")  True
ctx.Web.IsPropertyAvailable("Title")    False

测试用例3:同时加载标量和组合属性

ctx.Load(ctx.Web, w=>w.Lists,w=>w.Title);
ctx.ExecuteQuery();
//Results
ctx.Web.IsObjectPropertyInstantiated("Lists")  True
ctx.Web.IsPropertyAvailable("Title")    True


如何动态确定客户端对象属性是否已加载?

由于ClientObject.IsPropertyAvailableClientObject.IsObjectPropertyInstantiated方法需要将属性名称指定为字符串值,这可能会导致拼写错误。因此,我通常更喜欢使用以下扩展方法

public static class ClientObjectExtensions
{
    /// <summary>
    /// Determines whether Client Object property is loaded
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="clientObject"></param>
    /// <param name="property"></param>
    /// <returns></returns>
    public static bool IsPropertyAvailableOrInstantiated<T>(this T clientObject, Expression<Func<T, object>> property)
        where T : ClientObject
    {
        var expression = (MemberExpression)property.Body;
        var propName = expression.Member.Name;
        var isCollection = typeof(ClientObjectCollection).IsAssignableFrom(property.Body.Type);
        return isCollection ? clientObject.IsObjectPropertyInstantiated(propName) : clientObject.IsPropertyAvailable(propName);
    }
}

使用方法

using (var ctx = new ClientContext(webUri))
{

     ctx.Load(ctx.Web, w => w.Lists, w => w.Title);
     ctx.ExecuteQuery();


     if (ctx.Web.IsPropertyAvailableOrInstantiated(w => w.Title))
     {
         //...
     }

     if (ctx.Web.IsPropertyAvailableOrInstantiated(w => w.Lists))
     {
         //...
     }
} 

问题提到,即使Lists没有真正初始化,IsObjectPropertyInstantiated也会返回true。你有检查过吗? - Andrey Markeev
是的,至少在未请求Lists属性时它会返回false(测试用例1)。已针对SharePoint 2013进行测试。 - Vadim Gremyachev
2
这是一个非常好的扩展方法,运行得非常顺畅! - Mark
2
OfficeDev PnP现在包含EnsurePropertyEnsureProperties方法。 - rlv-dan
1
从C# 6开始,您还可以使用nameof关键字。 - Jigar

1

好的,这变得越来越复杂了,特别是在SharePoint Online中,即使没有抛出异常,Load和Execute方法的结果可能仍然不完整。然而,以下是我从这个和其他线程中收集到的内容,结合成为一个LoadAndExecute方法,可以作为ClientContext类的子类扩展或转换为静态扩展类。对于新的客户端对象,对象及其属性将在一次操作中加载,但结果将分别检查每个属性。对于现有的客户端对象,仅缺少的属性将在单独的操作中加载,这可能会不必要地消耗网络资源。因此,该方法不仅检查未初始化的属性,还尝试检索缺失的属性。此外,还有一个避免被限制的主题,即覆盖ClientContext的Execute方法,但这里没有包括:

/// <summary>
/// An extended ClientContext to avoid getting throttled.
/// </summary>
public partial class OnlineContext : ClientContext
{
    /// <inheritdoc />
    public OnlineContext(string webFullUrl, int retryCount = 0, int delay = 0)
        : base(webFullUrl)
    {
        RetryCount = retryCount;
        Delay = delay;
    }

    /// <summary>
    /// The retry count.
    /// </summary>
    public int RetryCount { get; set; }

    /// <summary>
    /// The delay between attempts in seconds.
    /// </summary>
    public int Delay { get; set; }

    /// <summary>
    /// Loads and executes the specified client object properties.
    /// </summary>
    /// <typeparam name="T">the object type.</typeparam>
    /// <param name="clientObject">the object.</param>
    /// <param name="properties">the properties.</param>
    /// <returns>true if all available, false otherwise.</returns>
    public bool LoadAndExecute<T>(T clientObject, params Expression<Func<T, object>>[] properties)
        where T : ClientObject
    {
        int retryAttempts = 0;
        int backoffInterval = Math.Max(Delay, 1);

        bool retry;
        bool available;
        do
        {
            if (clientObject is ClientObjectCollection)
            {
                // Note that Server Object can be null for collections!
                ClientObjectCollection coc = (ClientObjectCollection) (ClientObject) clientObject;
                if (!coc.ServerObjectIsNull.HasValue || !coc.ServerObjectIsNull.Value)
                {
                    available = coc.AreItemsAvailable;
                }
                else
                {
                    available = false;
                    break;
                }
            }
            else if (clientObject.ServerObjectIsNull.HasValue)
            {
                available = !clientObject.ServerObjectIsNull.Value;
                break;
            }
            else
            {
                available = false;
            }

            if (!available && retryAttempts++ <= RetryCount)
            {
                if (retryAttempts > 1)
                {
                    Thread.Sleep(backoffInterval * 1000);
                    backoffInterval *= 2;
                }

                Load(clientObject, properties);
                ExecuteQuery();
                retry = true;
            }
            else
            {
                retry = false;
            }
        } while (retry);

        if (available)
        {
            if (properties != null && properties.Length > 0)
            {
                foreach (Expression<Func<T, object>> property in properties)
                {
                    if (!LoadAndExecuteProperty(clientObject, property, retryAttempts > 0))
                    {
                        available = false;
                    }
                }
            }
        }
        return available;
    }

    /// <summary>
    /// Loads and executes the specified client object property.
    /// </summary>
    /// <typeparam name="T">the object type.</typeparam>
    /// <param name="clientObject">the object.</param>
    /// <param name="property">the property.</param>
    /// <param name="loaded">true, if the client object was already loaded and executed at least once.</param>
    /// <returns>true if available, false otherwise.</returns>
    private bool LoadAndExecuteProperty<T>(T clientObject, Expression<Func<T, object>> property, bool loaded = false)
        where T : ClientObject
    {
        string propertyName;
        bool isObject;
        bool isCollection;
        Func<T, object> func;
        Expression expression = property.Body;
        if (expression is MemberExpression)
        {
            // Member expression, check its type to select correct property test.
            propertyName = ((MemberExpression) expression).Member.Name;
            isObject = typeof(ClientObject).IsAssignableFrom(property.Body.Type);
            isCollection = isObject
                ? typeof(ClientObjectCollection).IsAssignableFrom(property.Body.Type)
                : false;
            func = isObject ? property.Compile() : null;
        }
        else if (!loaded)
        {
            // Unary expression or alike, test by invoking its function.
            propertyName = null;
            isObject = false;
            isCollection = false;
            func = property.Compile();
        }
        else
        {
            // Unary expression and alike should be available if just loaded.
            return true;
        }

        int retryAttempts = 0;
        int backoffInterval = Math.Max(Delay, 1);

        bool retry;
        bool available;
        do
        {
            if (isObject)
            {
                if (clientObject.IsObjectPropertyInstantiated(propertyName))
                {
                    ClientObject co = (ClientObject) func.Invoke(clientObject);
                    if (isCollection)
                    {
                        ClientObjectCollection coc = (ClientObjectCollection) co;
                        if (!coc.ServerObjectIsNull.HasValue || !coc.ServerObjectIsNull.Value)
                        {
                            available = coc.AreItemsAvailable;
                        }
                        else
                        {
                            available = false;
                            break;
                        }
                    }
                    else if (co.ServerObjectIsNull.HasValue)
                    {
                        available = !co.ServerObjectIsNull.Value;
                        break;
                    }
                    else
                    {
                        available = false;
                    }
                }
                else
                {
                    available = false;
                }
            }
            else if (propertyName != null)
            {
                available = clientObject.IsPropertyAvailable(propertyName);
            }
            else if (func != null)
            {
                try
                {
                    func.Invoke(clientObject);
                    available = true;
                }
                catch (PropertyOrFieldNotInitializedException)
                {
                    available = false;
                }
            }
            else
            {
                available = true; // ?
            }

            if (!available && retryAttempts++ <= RetryCount)
            {
                if (retryAttempts > 1)
                {
                    Thread.Sleep(backoffInterval * 1000);
                    backoffInterval *= 2;
                }

                Load(clientObject, property);
                ExecuteQuery();
                retry = true;
            }
            else
            {
                retry = false;
            }
        } while (retry);
        return available;
    }
}

1
Vadim Gremyachev提供的测试只覆盖了一半的场景 - 当您使用ctx.Load时的情况。但当您使用ctx.LoadQuery时,结果会发生变化:
var query = from lst in ctx.Web.Lists where lst.Title == "SomeList" select lst;
var lists = ctx.LoadQuery(query);
ctx.ExecuteQuery();
ctx.Web.IsObjectPropertyInstantiated("Lists") -> True
ctx.Web.Lists.ServerObjectIsNull -> False
ctx.Web.Lists.Count -> CollectionNotInitializedException

一旦在集合上调用了LoadQuery,你就不能再看到集合的可用性。

唯一的方法是检测异常是否发生。


0
使用扩展的想法很好,但只适用于列表。该扩展程序可以选择“对象”或“标量”属性。我认为这个扩展程序应该这样做得更好:
public static bool IsPropertyAvailableOrInstantiated<T>(this T clientObject, Expression<Func<T, object>> property)
    where T : ClientObject
{
    var expression = (MemberExpression)property.Body;
    var propName = expression.Member.Name;
    var isObject = typeof(ClientObject).IsAssignableFrom(property.Body.Type); // test with ClientObject instead of ClientObjectList
    return isObject ? clientObject.IsObjectPropertyInstantiated(propName) : clientObject.IsPropertyAvailable(propName);
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接