Linq to SharePoint. Part 5. Choice and MultiChoice fields

Part 1. First()/FirstOrDefault(), T-SQL IN, Path
Part 2. Count(), Take(), Skip(), JOIN, ObjectTracking
Part 3. Anonymous access, Resolve list by URL
Part 4. Dynamic LINQ, Convert SPListItem to data context object
Part 5. Choice and MultiChoice fields

Today's post is about Linq to SharePoint again. This time I'll show how to work with Choice and MultiChoice field.

enum for Choice field

enum for Choice field

Environment

First of all i need to say all examples are based on data model from my second post about Linq to SharePoint. There are two new fields in the Employee content type for this post. First of these - Sex (SPFieldChoice) and second one is Hobbies (SPFieldMultiChoice):

<Field ID="{68c1ee4a-5a25-4ccb-82ca-f5ff17e2016f}" Name="Sex" DisplayName="Sex" Type="Choice" Format="RadioButtons">
  <CHOICES>
    <CHOICE>Male</CHOICE>
    <CHOICE>Female</CHOICE>
  </CHOICES>
  <Default>Male</Default>
</Field>
<Field ID="{BD1898B4-0869-41F2-9DB0-B0F1B8F139D3}" Name="Hobbies" DisplayName="Hobbies" Type="MultiChoice" Mult="TRUE">
  <CHOICES>
    <CHOICE>Chess</CHOICE>
    <CHOICE>Football</CHOICE>
    <CHOICE>Basketball</CHOICE>
  </CHOICES>
  <Default>Chess</Default>
</Field>

Now after deploying this solution we can generate code using SPMetal tool. After this, we have two enumerators for these fields. One of these has Flags attribute (multichoice field):

public enum EmployeeSex
{
    None = 0,
    Invalid = 1,
    [Choice(Value = "Male")]
    Male = 2,
    [Choice(Value = "Female")]
    Female = 4
}
 
[FlagsAttribute]
public enum EmployeeHobby
{
    None = 0,
    Invalid = 1,
    [Choice(Value = "Chess")]
    Chess = 2,
    [Choice(Value = "Football")]
    Football = 4,
    [Choice(Value = "Basketball")]
    Basketball = 8
}

Single Choice

Let's begin from simple cases when a field can have only one value. SPMetal generates code which we can just use. If we need to select male employees we use code like following:

using (var ctx = new ZhukDataContext(siteUrl))
{
    var employees = ctx.Employees
        .Where(emp => emp.Sex == EmployeeSex.Male)
        .ToList();
    // ...
}

But in this case SPLinqProvider can't convert this lambda function to CAML query. Before filtering data SPLinqProvider retrieves all data from a list (or document library) and after this, it'll be filtering using Linq to Object. The reason is this: values of Choice and MultiChoice fields are stored in a content database as text values (separated by ";#" in a multichoice case).

To work around this limitation I use this trick: I change the type of property from enumerator to string. For support enumerators you can create additional calculated property without ColumnAttribute attribute:

public EmployeeSex? Sex
{
    get
    {
        var res = Enum.Parse(typeof (EmployeeSex), _sexValue);
        return res is EmployeeSex ? (EmployeeSex) res : EmployeeSex.Invalid;
    }
}
 
[Column(Name = "Sex", Storage = "_sexValue", FieldType = "Choice")]
public string SexValue
{
    get
    {
        return _sexValue;
    }
    set
    {
        if ((value == _sexValue)) return;
        var vals = Enum.GetValues(typeof(EmployeeSex));
        foreach (EmployeeSex val in vals)
        {
            if (!string.Equals(Enum.GetName(typeof(EmployeeSex), val), value,
                                StringComparison.InvariantCultureIgnoreCase)) continue;
            OnPropertyChanging("SexValue", _sexValue);
            _sexValue = value;
            OnPropertyChanged("SexValue");
        }
    }
}

Now filtering by text value (SexValue property) of field is available for us. And the result will be the right CAML-query. To escape texts in your code you may use extension method for enums, which returns a value from ChoiceAttribute of enumerator' member:

public static string GetChoiceValue(this Enum enumerator)
{
    // Get the type
    var type = enumerator.GetType();
    // Get the name of field
    var fieldName = Enum.GetName(enumerator.GetType(), enumerator);
    // Get the field
    var field = type.GetField(fieldName, 
                BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public);
    // Get attributes of the field
    var attributes = field.GetCustomAttributes(typeof (ChoiceAttribute), true);
    var attribute = attributes.FirstOrDefault();
    // If the attribute is null return empty text
    // In other case return Value property of it
    return attribute == null
                ? string.Empty
                : ((ChoiceAttribute) attribute).Value;
}

Now everything works properly and looks quite nice:

using (var ctx = new ZhukDataContext(siteUrl))
{
    var employees = ctx.Employees
        .Where(emp => emp.SexValue == EmployeeSex.Male.GetChoiceValue())
        .ToList();
}

CAML query generated in both cases I'll not give there - it needs a lot of space. You can try this by taking the demo project.

Multiple Choice

If a field can have multiple values this trick doesn't work (at least I can't work this around). I think the key to this trouble is this: values of MultiChoice field stored in content database as separated by delimeter (;#):

One of solution is converting enumerators to string. You can use for this extension method like EqualsAny, desribed by myself in 4th part. Th new method checks on the contents of value in row:

public static Expression<Func<T, bool>> ContainsAny<T>(this Expression<Func<T, string>> selector,
    IEnumerable<string> values)
{
    // Return x=> false expression if value is empty
    if (!values.Any()) return x => false;
    // Do the same in case count of parameters doesn't equal one
    if (selector.Parameters.Count != 1) return x => false;
    var p = selector.Parameters.First();
    // Get the reference to Contains method
    var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    // Build expression containing String.Contains method for each value
    var equals = values
        .Select(v => (Expression)Expression.Call(
            selector.Body, method, Expression.Constant(v, typeof(string))));
    // Aggregate expressions
    var body = equals.Aggregate(Expression.Or);
    // Return the expression
    return Expression.Lambda<Func<T, bool>>(body, p);
}

Source code of demo project for this post is available here

I hope my posts about using Linq to SharePoint will be helpful.

Vitaly Zhukov

Vitaly Zhukov

Tech Lead, Architect, Developer, Technical Trainer, Microsoft MVP. Over 20 years of experience in system integration and software development. I specialize in designing and implementing scalable, high-performance software solutions across various industries.

You May Also Like

Call Dataverse API from SPFx Web Part

Call Dataverse API from SPFx Web Part

Provision Lists and Libraries with SPFx solution

Provision Lists and Libraries with SPFx solution

SharePoint. Drag-and-Drop File Uploading

SharePoint. Drag-and-Drop File Uploading

CSOM. Upload document

CSOM. Upload document

SharePoint List REST API. Part 2

SharePoint List REST API. Part 2

SharePoint Framework. Create Angular WebPart

SharePoint Framework. Create Angular WebPart

SharePoint List REST API. Part 1

SharePoint List REST API. Part 1

Project Server. CSOM + Custom Fields

Project Server. CSOM + Custom Fields

SharePoint 2010. Long time operation with updatable status

SharePoint 2010. Long time operation with updatable status

Linq to SharePoint. Cross site collection queries

Linq to SharePoint. Cross site collection queries

SharePoint. Getting Document Icon URL

SharePoint. Getting Document Icon URL

Linq to SharePoint. Repository pattern

Linq to SharePoint. Repository pattern

Linq to SharePoint vs Camlex.NET Performance Comparison

Linq to SharePoint vs Camlex.NET Performance Comparison

Linq to SharePoint. Part 4

Linq to SharePoint. Part 4

Linq to SharePoint. Part 3

Linq to SharePoint. Part 3

Linq to SharePoint. Part 2

Linq to SharePoint. Part 2

Linq to Sharepoint. Part 1

Linq to Sharepoint. Part 1