6

I am using ArangoDB and I am querying a collection named movies. Its data structure is such that categories is a List of strings.

public class movies
{
    [DocumentProperty(Identifier = IdentifierType.Key)]
    public string Key;
    public List<string> categories;
    public string desc;
    public string img;
    public List<vod_stream> streams;
    public string title;
};

Here is the query statement:

var result = db.Query<movies>()
            .Where(p => p.categories.Contains(id));

id is passed as a param and I need to retrieve all the movies that has the category matched by the id. However, above code doesn't work as result gives me ALL the movies in the collection.

foreach (movies mov in result)
{
    if (mov.categories.Contains(id) == false)
    { continue; }

    // do something here
}

The weird thing is when I loop through the items in the result, the same function does return false for some of the items. But it just doesn't work in the Linq statement.

Anybody knows what is wrong with my query statement?

Darren
  • 152
  • 9
  • I can´t see why the second code works while the former does not. Are you sure you provided the exact code? MAybe you forgot some `!`-operator. Anyway you can shorten you second condition to `if(!mov.categories.Contains(id))`. – MakePeaceGreatAgain Sep 12 '17 at 08:18
  • Contains calls the Equals-method of the objects in the list. If your class `movies` does not override Equals properly, it will by default return false unless its the same object (i.e. the internal reference numer is the same). – Chrisi Sep 12 '17 at 08:24
  • @Chrisi - categories is a list of strings so I don't think it is the case – Gilad Green Sep 12 '17 at 08:31
  • i have updated my question so that it is more clear – Darren Sep 12 '17 at 08:36
  • The two snippets have very little in common. The first is a *database* query, the second isn't even a query, it's just an iteration over a list. *What* do you want to do? Find all movies that belong to specific categories? What is the query generated by the first snippet? – Panagiotis Kanavos Sep 12 '17 at 08:47
  • @PanagiotisKanavos that is correct, I want to yield a result of all movies that belong to specific category. The query in the first snippet generated every single movie in the db collection, not what i wanted. – Darren Sep 12 '17 at 08:54
  • @Darren what *was* the generated query? What was sent to the database? ArangoDB may do what LINQ2SQL did - load everything it doesn't understand and try to filter it using LINQ-to-Objects. If, as Jamiec says, it requires a special `AQL.Contains` method, the database may be simply ignoring the `Categories.Contains` clause – Panagiotis Kanavos Sep 12 '17 at 08:57
  • @PanagiotisKanavos yes I agree with you the database may be ignoring the `Categories.Contain` clause. I hope you should understand what I wish to achieve by now. Which I do not know what is the correct query statement I should write to achieve that. – Darren Sep 12 '17 at 09:09

3 Answers3

5

For using AQL Functions in ArangoDB LINQ provider you should use ArangoDB.Client.AQL static methods.

For your use-case you can use in AQL function:

var result = db.Query<movies>()
                .Where(p => AQL.In(id, p.categories))
                .ToList();

which translates to :

for `p` in `movies`  filter  @P1 in `p`.`categories`  return   `p`

Built-in methods for c# data structures like List are not currently supported in ArangoDB LINQ provider

raoof hojat
  • 355
  • 4
  • 12
3

LINQ can be a strange beast. If you're used to using LINQ against straight objects there would appear to be nothing wrong with your code. However, your code which does not work is translating the LINQ into a database query.

The difference in the one that works is that by doing a foreach over the database queried data, you're materializing the data and reverting back to Objects.

I looked at the docs do the arrangodb client as I am not familiar with it and one bit stood out:

int age =  db.Query<Person>()
                  .Where(p => AQL.Contains(p.Name, "raoof"))
                  .Select(p => p.Age)
                  .FirstOrDefault();

Note that AQL.Contains - it is similar to the way LINQ-to-SQL used to support database functions for SQLServer. So id suggest there is an AQL.<something> method which will do what you want. Unfortunately their documentation is not the best and I'm struggling to find exactly what you need. You might try something along the lines of

var result = db.Query<movies>()
        .Where(p => AQL.Contains(p.categories,id));
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • The second snippet in the question isn't even a LINQ query. The equivalent LINQ query would look identical to the database query `result.Where(p => p.categories.Contains(id))`. It could still give different results because .NET string comparisons are case sensitive while databases often use case-insensitive collations – Panagiotis Kanavos Sep 12 '17 at 08:50
  • @PanagiotisKanavos Im not sure I follow what you're saying. It doesnt appear to be asking for clarification on my answer so seems a bit irrelevant. – Jamiec Sep 12 '17 at 08:53
  • it's a comment. Not all comments are *asking* for clarification. That doesn't make them irrelevant – Panagiotis Kanavos Sep 12 '17 at 08:54
  • @PanagiotisKanavos in which case it seems like it should have been a comment *on the question*. Its got nothing to do with my answer! – Jamiec Sep 12 '17 at 08:55
  • I agree, their documentation is very limited.. :( – Darren Sep 12 '17 at 08:59
-1

When I added a quick code, this does works

public void GetMoviesByCategory()
    {

        List<Movies> movies = new List<Movies>();
        movies.Add(new Movies(){moviename = "A", category = new List<String>(){"1","2"}});
        movies.Add(new Movies(){moviename = "B", category = new List<String>(){"2","3"}});
        movies.Add(new Movies(){moviename = "C", category = new List<String>(){"3","4"}});
        movies.Add(new Movies(){moviename = "D", category = new List<String>(){"1"}});
        movies.Add(new Movies(){moviename = "E", category = new List<String>(){"4"}});
        movies.Add(new Movies(){moviename = "F", category = new List<String>(){"1","2","4"}});
        var result = movies.Select(m => m).Where(p => p.category.Contains("1")).ToList();

        foreach(var movie in result){
            Console.WriteLine(movie.moviename);
        }
    }
    public class Movies{

        public Movies(){}
        public string moviename {get;set;}
        public List<string> category {get;set;}
    }
TechGirl
  • 478
  • 2
  • 12
  • 2
    This is using linq directly with objects. the OP Is using a Linq implementation against a database. Its a totally different thing – Jamiec Sep 12 '17 at 08:41