Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ .Take() returns more elements than requested

Tags:

c#

linq

entities

We have a simple LINQ-to-Entities query that should return a specific number of elements from particular page. The example of the request can be:

var query = from r in records
            orderby r.createdDate descending
            select new MyObject()
            { ... };

//Parameters: pageId = 8, countPerPage = 10
List<MyObject> list = query.Skip(pageId * countPerPage).Take(countPerPage);

The above example works great in most of the cases, but sometimes the list has more than 10 elements. This doesn't seem to be always true and depends from the database data. For example, when we request the page 10 and pass countPerPage as 10, we're getting 10 elements. But when we request the page 12 and pass countPerPage as 10, we're getting 11 elements. Then when we ask for page 21, we're getting 10 elements once again.

Is there any possible reason why that happens?

UPDATE: The query, for sure, is not so simple, as it is in example, and contains the sub-queries.

And here's a more complete example:

var elementsQuery = from m in entityContext.elements
                    where m.elementSearchText.Contains(filter)
                    orderby m.CreatedDate descending
                    select new DataContracts.ElementForWeb()
                    {
                        FirstName = m.FirstName,
                        LastName = m.LastName,
                        Photos = (from p in m.Photos select p.ID),
                        PlacesCount = m.Childs.Where(x => x.Place != null).Count() + ((m.MainChild != null)?1:0),
                        SubElements = (
                            from t in m.Childs
                            orderby t.CreatedDate descending
                            select new DataContracts.ChildForWeb()
                            {
                                CommentsCount = t.ChildComments.Count,
                                Photos = (from p in t.Photos select p.ID),
                                Comments = (from c in t.ChildComments
                                orderby c.CreatedDate descending
                                select new DataContracts.CommentForWeb()
                                {
                                    CommentId = c.ID,
                                    CommentText = c.CommentText,
                                    CreatedByPhotoId = c.Account.UserPhoto,
                                    CreatedDate = c.CreatedDate,
                                }).Take(5)
                            }).Take(5)
                      };

List<DataContracts.ElementForWeb> elements = 
    new List<DataContracts.ElementForWeb>(
        elementsQuery
           .Skip(pageId * countPerPage)
           .Take(countPerPage));

UPDATE2: Here's even more interesting test.

        for (var i = 0; i < 10; i++) {
            Service.GetElementsForWebPaged(12, 10, "",
                function (result) {
                    console.log("Elements returned: " + result.length);
                },
                function (error) {
                });
        }

The results are "awesome"!

Elements returned: 11
Elements returned: 11
Elements returned: 10
Elements returned: 11
Elements returned: 11
Elements returned: 10
Elements returned: 11
Elements returned: 10
Elements returned: 11
Elements returned: 11
like image 573
Denis Mazourick Avatar asked Aug 16 '12 08:08

Denis Mazourick


People also ask

What is skip and Take in LINQ?

The Take operator is used to return a given number of elements from an array and the Skip operator skips over a specified number of elements from an array. Skip, skips elements up to a specified position starting from the first element in a sequence.

What is Take () in C#?

C# Queryable Take() Method Get specified number of elements from the beginning using the Take() method. The following is our array. int[] marks = { 35, 72, 50, 90, 95, 85, 52, 67 }; Now, use OrderByDescending to order the elements in Descending order. Then use the Take() method to get the elements.

Which Linq method allows you to specify the number of items to return in a sequence?

Repeat Method is used to generate a collection of IEnumerable<T> type with a specified number of elements and each element contains same specified value. Second Paramter "int count" represents-> Number of times needed to repeat the generated element of sequnce.

What does Linq Select Return?

By default, LINQ queries return a list of objects as an anonymous type. You can also specify that a query return a list of a specific type by using the Select clause.


Video Answer


1 Answers

It would be difficult to test this answer because it depends on your schema and test data, etc. But I believe you may be having a problem mixing up IQueryAble results with IEnumerable results.

Remember, an linq-To-Entities query doesn't actually do a roundtrip to the database until a foreach or ToList() is done.

I would suggest first breaking this into pieces:

var elementsQuery = from m in entityContext.elements
                    where m.elementSearchText.Contains(filter)
                    orderby m.CreatedDate descending;

var elements = elementsQuery.Skip(pageId * countPerPage).Take(countPerPage)).ToList();

Then build you projection...

var elementsForWeb = from m in elements
                     select new DataContracts.ElementForWeb()
                     {
                     ...
                     }
like image 73
oshea00 Avatar answered Oct 16 '22 10:10

oshea00