Proper way to detect if a ClientObject property is already retrieved/initialized
Asked Answered
C

4

15

If you're using the Client Object Model from SharePoint and access properties which haven't been initialized or already retrieved by an

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

you get for example a:

Microsoft.SharePoint.Client.PropertyOrFieldNotInitializedException

or

The collection has not been initialized. It has not been requests or the request has not been executed.

Exception.

Is there any proper way to check before accessing these properties if they are already initialized/retrieved? Without a Try/Catch approach. I don`t like that one's.

I want to check before a Exception has been thrown and handle it.

I already checked the

IsObjectPropertyInstantiated

IsPropertyAvailable

Methods but they don't help really. IsPropertyAvaiable only checks scalar properties and won't give a result on for example Web.Lists and IsObjectPropertyInstantiated returns true for Web.Lists although Web.Lists was not initialized.

Comeback answered 5/9, 2014 at 6:45 Comment(0)
A
29

I would say your question is already contains the correct answer to some extent.

In order to determine whether client object property is loaded or not the following methods are available:

Tests

Test case 1: load scalar property only

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

Test case 2: load composite property only

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

Test case 3: load both scalar and composite properties

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


How to dynamically determine whether client object property is loaded or not?

Since ClientObject.IsPropertyAvailable and ClientObject.IsObjectPropertyInstantiated methods expect the property name to be specified as a string value and that could lead to typos, I usually prefer the following extension method:

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

Usage

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))
     {
         //...
     }
} 
Authoritarian answered 5/9, 2014 at 9:20 Comment(5)
The question mentions that IsObjectPropertyInstantiated returns true even if Lists was not really initialized. Did you check it?Frye
Yep, at least it returns false when Lists property is not requested (test case 1). Tested against SharePoint 2013Authoritarian
that's a nice little extension method, which is working like a charm!Comeback
OfficeDev PnP now contains EnsureProperty and EnsureProperties methods.Hodgkinson
Starting c# 6 you can also use nameof keywordCherry
H
1

The tests provided by Vadim Gremyachev only cover one half of the scenarios - where you use ctx.Load. But when you use ctx.LoadQuery the result changes:

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

So once the LoadQuery has been called on a collection, you can no longer see if the collection is actually available.

Only way in this case is to detect that the exception occurs.

Halogenate answered 24/9, 2014 at 9:10 Comment(0)
M
1

OK, this is getting more and more complicated especially with SharePoint Online where results of Load and Execute methods may be incomplete even without thrown exceptions. However, below is what I collected from this and other threads combined to a LoadAndExecute method that can be a subclass extension to the ClientContext class or be converted to a static extension class. For a new client object, the object and its properties are loaded in one operation but the result is checked separately for each property. For an existing client object, only missing properties are loaded in separate operations which may consume network resources unnecessarily. So the method doesn't only check which properties are not initialized but tries to retrieve the missing ones, too. In addition, there is another topic of avoiding being throttled by overriding the Execute method of the ClientContext, but that is not included here:

/// <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;
    }
}
Mellifluent answered 4/2, 2021 at 13:3 Comment(0)
C
0

The idea of using a extension is great, but only works fine with lists. The extension may choose between "object" and "scalar" properties. I think than the extension will be better this way:

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

Celestine answered 31/1, 2019 at 8:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.