Folders first in paged results using CamlQuery and ListItemCollectionPosition - sorting

I'm implementing paging using CamlQuery and ListItemCollection, sorting results by the name field. I would like folders to come first, as they do in UI, e.g. "folder A2, folder B2, file A1, file B1", but instead I get "file A1, folder A2, file B1, folder B2".
What is the best way to accomplish such sorting and paging? Note that for paging I have to specify the value of the sorting field which will be the first record of the page – I've considered adding two sorting fields into CAML, but I'm not sure whether I can use two fields in ListItemCollectionPosition.PagingInfo.
The code I'm currently using is like this:
var queryText = #"
<View>
<Query>
<OrderBy Override='TRUE'>
<FieldRef Name='FileLeafRef' Ascending='True' />
</OrderBy>
</Query>
...
<RowLimit>
10
</RowLimit>
</View>";
var camlQuery = new CamlQuery();
camlQuery.ViewXml = queryText;
camlQuery.ListItemCollectionPosition = new ListItemCollectionPosition
{
PagingInfo = "Paged=TRUE&p_ID=1002&p_FileLeafRef=A1"
};
var items = list.GetItems(camlQuery);

For getting results sorted by object type you could utilize FSObjType property, for example the following CAML expressions tells server to return folder items first and then file items:
<OrderBy Override='TRUE'>
<FieldRef Name='FSObjType' Ascending='False' />
</OrderBy>
Regarding ListItemCollectionPosition.PagingInfo property, the following expression tells to return items that come after the item with ID specified via p_ID parameter and sorted by object type:
var camlQuery = new CamlQuery();
camlQuery.ListItemCollectionPosition = new ListItemCollectionPosition
{
PagingInfo = "Paged=TRUE&p_FSObjType=1&p_ID=200"
};
Example
The following example returns 200 items:
with with ID starting from 100
and sorted by object type (folder items comes first)
Code:
var itemsCount = 200;
var startItemId = 100;
var queryText = #"
<View>
<Query>
<OrderBy Override='TRUE'>
<FieldRef Name='FSObjType' Ascending='False' />
</OrderBy>
</Query>
<RowLimit>
{0}
</RowLimit>
</View>";
var camlQuery = new CamlQuery
{
ViewXml = string.Format(queryText, itemsCount),
ListItemCollectionPosition = new ListItemCollectionPosition
{
PagingInfo = $"Paged=TRUE&p_FSObjType=1&p_ID={startItemId - 1}"
}
};
var items = list.GetItems(camlQuery);
ctx.Load(items);
ctx.ExecuteQuery();
Update
The example of PagingInfo expression for items to be sorted by type first and then by name:
Paged=TRUE&p_FSObjType=1&p_FileLeafRef=B2&p_ID=100

Related

How to create a predicate that must meet two values with one being a list of values?

With the following code how do I get a return that includes any of zip list but only records that have a purpose of street address? This currently returns matches for either the zip or the street address.
var zipPredicate = PredicateBuilder.False<NameAddress>();
List<string> zips = new List<string>();
zips.Add("90210");
zips.Add("90211");
foreach (var item in zips)
{
zipPredicate = zipPredicate.Or(n=> n.ZIP.Contains(item));
}
zipPredicate = zipPredicate.And(n=> n.Purpose=="Street Address");
var zipResult = from s in NameAddresses
.AsExpandable()
.Where(zipPredicate)
select new{s.ID, s.ZIP, s.Purpose};
zipResult.Dump();
I think what you are looking for is:
var zipPredicate = PredicateBuilder.False<NameAddress>();
List<string> zips = new List<string>();
zips.Add("90210");
zips.Add("90211");
foreach (var item in zips)
{
zipPredicate = zipPredicate.Or(n=> n.ZIP.Contains(item) && n.Purpose=="Street Address");
}
var zipResult = from s in NameAddresses
.AsExpandable()
.Where(zipPredicate)
select new{s.ID, s.ZIP, s.Purpose};
zipResult.Dump();
EDIT
One more thing as well, if you want to drop building up the predicate, you should be able to do something like: .Where(n=>zips.Contains(n.ZIP) && n.Purpose=="Street Address") The important piece is that your entity property component comes inside the .Contains(). This would shorten your code to:
List<string> zips = new List<string>();
zips.Add("90210");
zips.Add("90211");
var zipResult = from s in NameAddresses
.AsExpandable()
.Where(n=>zips.Contains(n.ZIP) && n.Purpose=="Street Address")
select new{s.ID, s.ZIP, s.Purpose};
zipResult.Dump();
Which I like better for readability. I would expect the query that ends up getting executed is the same either way.

Filter SelectListItem value using some condition

I have one SelectListItem for DropDownList. I have to filter based on some condition. If I try adding the condition then its gives me an error like this (LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression). I ll be adding that code here. Please guide me to solve this.
Code
IEnumerable<SelectListItem> IssueId = (from txt in Db.Issues where txt.BibId == BibId
select new SelectListItem()
{
Text = txt.Description,
Value = txt.Id.ToString(),
Selected = true,
});
SelectList IssueIds = new SelectList(IssueId, "Value", "Text");
ViewBag.IssueId = IssueIds;
Thanks
Try this:
LINQ2EF does not know ToString() but after AsEnumerable() you'll get a local collection when ToString() is implemented.
IEnumerable<SelectListItem> IssueId =
(from txt in Db.Issues.Where(e => e.BibId == BibId).AsEnumerable()
select new SelectListItem()
{
Text = txt.Description,
Value = txt.Id.ToString(),
Selected = true
});
Linq To Sql can't generate TSQL for txt.Id.ToString()
You will need to iterate the result instead after executing the query, or cast to Enumerable as xeondev suggests.
That extension does not seem to be sorted by linq to Entities but you could just do the mapping once you have the issues, e.g.
var issues = (from issue in Db.Issues
where issue .BibId == BibId
select issue ).ToList();
IEnumerable<SelectListItem> IssueId = (from txt in issues
where txt.BibId == BibId
select new SelectListItem()
{
Text = txt.Description,
Value = txt.Id.ToString(),
Selected = true,
});

Pass a List of Series to SetSeries

I am using DotNet.Highcharts in conjunction with Visual Studio 2010. I have created an array of Series:
List<Series> allSeries = new List<Series>();
I then looped through a database and added several different Series. Then I created a Highchart and need to add the allSeries list to it. I have the code below that I used to create a Series one at a time. How can I take the allSeries list and pass it to SetSeries?
.SetSeries(new[]
{
new Series { Name = "Combiner 2", Data = new Data(myData2) },
new Series { Name = "Combiner 3", Data = new Data(myData3) }
});
if I am left to assume that the myData2 and myData3 objects are contained in or could be extracted from allSeries, then you should be able to do something like this:
.SetSeries(allSeries.Select(s=> new Series { Name = s.Name, Data = s.Data }));
EDIT:
If set series isn't looking for an IEnumerable<Series> but instead needs Object[] or Series[], then you could do this:
//casts series elements to object, then projects to array
.SetSeries(allSeries.Select(s=> (object)new Series { Name = s.Name, Data = s.Data }).ToArray());
or maybe this:
//projects series elements to array of series
.SetSeries(allSeries.Select(s=> new Series { Name = s.Name, Data = s.Data }).ToArray());
it all depends on what the method signature for SetSeries is.

Complex foreach loop possible to shorten to linq?

I have a cluttery piece of code that I would like to shorten using Linq. It's about the part in the foreach() loop that performs an additional grouping on the result set and builds a nested Dictionary.
Is this possible using a shorter Linq syntax?
var q = from entity in this.Context.Entities
join text in this.Context.Texts on new { ObjectType = 1, ObjectId = entity.EntityId} equals new { ObjectType = text.ObjectType, ObjectId = text.ObjectId}
into texts
select new {entity, texts};
foreach (var result in q)
{
//Can this grouping be performed in the LINQ query above?
var grouped = from tx in result.texts
group tx by tx.Language
into langGroup
select new
{
langGroup.Key,
langGroup
};
//End grouping
var byLanguage = grouped.ToDictionary(x => x.Key, x => x.langGroup.ToDictionary(y => y.PropertyName, y => y.Text));
result.f.Apply(x => x.Texts = byLanguage);
}
return q.Select(x => x.entity);
Sideinfo:
What basically happens is that "texts" for every language and for every property for a certain objecttype (in this case hardcoded 1) are selected and grouped by language. A dictionary of dictionaries is created for every language and then for every property.
Entities have a property called Texts (the dictionary of dictionaries). Apply is a custom extension method which looks like this:
public static T Apply<T>(this T subject, Action<T> action)
{
action(subject);
return subject;
}
isn't this far simpler?
foreach(var entity in Context.Entities)
{
// Create the result dictionary.
entity.Texts = new Dictionary<Language,Dictionary<PropertyName,Text>>();
// loop through each text we want to classify
foreach(var text in Context.Texts.Where(t => t.ObjectType == 1
&& t.ObjectId == entity.ObjectId))
{
var language = text.Language;
var property = text.PropertyName;
// Create the sub-level dictionary, if required
if (!entity.Texts.ContainsKey(language))
entity.Texts[language] = new Dictionary<PropertyName,Text>();
entity.Texts[language][property] = text;
}
}
Sometimes good old foreach loops do the job much better.
Language, PropertyName and Text have no type in your code, so I named my types after the names...

linq: Using methods in select clause

I'm breaking my head with this and decided to share my problem with you
I want to create an anonymous select from several tables, some of them may contain more than one result. i want to concatenate these results into one string
i did something like this:
var resultTable = from item in dc.table
select new
{
id= item.id,
name= CreateString((from name in item.Ref_Items_Names
select name.Name).ToList()),
};
and the CreateString() is:
private string CreateString(List<string> list)
{
StringBuilder stringedData = new StringBuilder();
for (int i = 0; i < list.Count; i++)
{
stringedData.Append(list[i] + ", ");
}
return stringedData.ToString();
}
my intentions were to convert the "name" query to list and then sent it to CreateString() to convert it to one long concatenated string.
I tried using .Aggregate((current,next) => current + "," + next);
but when i try to convert my query to DataTable like below:
public DataTable ToDataTable(Object query)
{
DataTable dt = new DataTable();
IDbCommand cmd = dc.GetCommand(query as IQueryable);
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = (SqlCommand)cmd;
cmd.Connection.Open();
adapter.Fill(dt);
cmd.Connection.Close();
return dt;
}
I'm getting exception that "dc.GetCommand()" can't understand query with Aggregate method
later I tried to even use this simple query:
var resultTable = from itemin dc.table
select new
{
name = CreateString()
};
When CreateString() returns "success", nothing was inserted to "name"
why there is no way of using methods in select clause?
Thank you
Yotam
There is difference between LINQ to objects and LINQ to some-db-provider. Generally speaking, when using IQueryable, you can't use any methods, except the ones your provider understands.
What you can do is to retrieve the data from the database and then do the formatting using LINQ to objects:
var data = from item in dc.table
where /* some condition */
select item;
var result = from item in data.AsEnumerable()
select new
{
name = SomeFunction(item)
}
The AsEnumerable() extension method forces processing using LINQ to objects.
Forgive me if I've miss interpreted your question. It seems that what you are trying to do is abstract your select method for reuse. If this is the case, you may consider projection using a lambda expression. For example:
internal static class MyProjectors
{
internal static Expression<Func<Object1, ReturnObject>> StringDataProjector
{
get
{
return d => new Object1()
{
//assignment here
}
}
}
}
Now you can select your datasets as such:
dc.Table.Select(MyProjectors.StringDataProjector)
As for the concatenation logic, what about selecting to some base class with an IEnumerable<string> property and a read-only property to handle the concatenation of the string?

Resources