If a linq query returns empty does it return null? - linq

I have the following linq query:
vm.logs = (from l in db.ActivityLogs
orderby l.Time
select l).Take(2);
If the db table is empty will this return null?
If not how can I detect if a query did return any information?

It will return an IEnumerable<ActivityLog> with no elements.
To check if there are any elements, use the Any() method:
if(!logs.Any())
{
Console.WriteLine("No elements found.");
}
Also note that as you've written it, vm.logs will be lazily evaluated, that is, it won't be fetched from the database until it is used. If you first do a .Any() and then later access the contents of the query, there will be two queries executed in the database. To avoid that, materialize (force the query to execute) by adding a ToList() to the query:
vm.logs = (from l in db.ActivityLogs
orderby l.Time
select l).Take(2).ToList();

Related

Is it possible to use method in the group by method in Linq?

I am trying to group by my custom method. For example, if the group id is something, then I want to return 1 or 0 from the method of GetClientGroup, then I want to group by the value. But I am getting error such as this.
Error
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
await (from o in _cdsContext.Order
where o.ClienteleId == clienteleId && o.DeliveryDate >= new DateTime(2020, 06, 29).Date
&& o.DeliveryDate != null
group o by new
{
o.ClienteleId,
o.DeliveryDate,
ClientGroup= o.OrderTypeId == 22 ? 259 : GetClientGroup(clienteleId, (int)o.GroupId),
}
into g
select new { ClienteleId = g.Key.ClienteleId}).ToListAsync()
I think you get this error at run time, not at compile time. Am I right?
IEnumerable and IQueryable
You should be aware of the difference between IEnumerable<...> and IQueryable<...>.
Object that implement IEnumerable<...> or IQueryable<...> represents the potentional to give you an enumerable sequence. Once you've got the sequence, you can ask for the first element, and once you've got this, you can ask for the next element as long as there is an element.
This iterating over the elements is usually done using a foreach (var element in sequence) {...}. This translates into the following:
IEnumerable<MyType> sequence = ... // the potential to get iterator
IEnumerator<MyType> enumerator = sequence.GetEnumerator(); // get the iterator
while (enumerator.MoveNext()) // iterate
{ // as long as there are items
MyType item = enumerator.Current; // fetch the item
ProcessItem(item); // and process it.
}
The LINQ methods that don't return IEnumerable<...> or IQueryable<...>, like ToList, ToDictionary, Count, Any, FirstOrDefault, etc internally all use foreach or GetEnumerator
An object that implements IEnumerable<...> is meant to be processed by your local process. The object holds everything to be able to iterate, inclusive calls to local methods.
On the other hand, an object that implements IQueryable<...>, like your _cdsContext.Order is meant to be processed by another process, usually a database management system.
This object holds an Expression and a Provider. The Expression is a generic form of the data that you want to query. The Provider knows who has to execute the query, and what language is used (usually SQL)
Concatenating LINQ statements won't execute the query, they will only change the Expression. When (deep inside) GetEnumerator() is called, the Expression is sent to the Provider, who will translate it into SQL and execute the query at the DBMS. The fetched data is represented as an iterator to your process, who will repeatedly call MoveNext() and Current.
Back to your question
Your GroupBy contains a call to a local method. The GroupBy won't execute the query, it will only change the Expression. In the end you do a ToList. The Tolist will do a GetEnumerator(). The Expression is sent to the Provider who will try to translate it into SQL.
Alas, your provider doesn't know your local method GetClientGroup, and thus can't convert it into SQL. In fact, apart from all your local methods, there are also several LINQ methods that can't be translated into SQL. See Supported and Unsupported LINQ methods (LINQ to entities)
Your compiler doesn't know which methods the provider can translate, so the compiler won't complain. Only at run time, when you do a ToList, the problem is detected.
How to solve the problem
The problem is in parameter KeySelector of Queryable.GroupBy
Expression<Func<TSource,TKey>> keySelector
Alas you forgot to write what GetClientGroup does. It seems that it takes the ClienteleId and the GroupId of an Order, and returns an integer that is similar to a ClientGroup.
The most easy would be to replace the call to GetClientGroup with the code that is in that method. Don't call any other methods
DateTime deliveryLimitDate = new DateTime(2020, 06, 29).Date;
var result = dbContext.Orders
.Where (order => order.ClienteleId == clienteleId
&& order.DeliveryDate != null
&& order.DeliveryDate >= deliveryLimitDate)
.GroupBy(order => new // Parameter KeySelector
{
ClienteleId = order.ClienteleId,
DeliveryDate = order.DeliveryDate,
ClientGroup= order.OrderTypeId == 22 ? 259 :
// formula in GetClientGroup(...)
// for example
(int)order.GroupId << 16 + order.ClienteleId
// parameter ResultSelector
group => new { ClienteleId = group.Key.ClienteleId});
Instead of a separate Select, I used the GroupBy overload with a parameter ResultSelector. Your result is a sequence of objects with only one property ClienteleId. Consider to return only a sequence of ClienteleId:
// parameter ResultSelector
group => group.Key.ClienteleId});
Alas, since I don't know your GetClientGroup, I can't give you parameter KeySelector

How to use the for loop over multiple criteria in mongodb from spring

I am trying to use the for loop over the criteria in the spring Dao to return a list of documents for each scenario matched in the list:
mongoDb contains multiple documents for the same studentId with different times.
I have a Array list with studentId's and times that I want to query on mongodb
The input looks like this
List <StudentAssociation> ={{studentId:st1234,assocstarttime=2019-08-16,assocendtime:2019-08-17},{studentId:st3456,assocstarttime=2019-07-09, assocendtime=2019-08-15}}
The query I wrote for a single student working without the for loop, but I want to send the list as the input
Working query
public List<Student> getListOfStudentRecords(String studentId, LocalDateTime assocstarttime, LocalDatetime assocendtime) {
Query query = new Query();
Criteria studentIdCriteria = Criteria
.where("studentId)
.is(studentId);
Criteria dateTimeCriteria = Criteria
.where(datetime)
.gte(assocstarttime.toString())
.lte(assocendtime.toString());
query.addCriteria(studentIdCriteria);
query.addCriteria(dateTimeCriteria);
return mongoTemplate.find(query, Student.class);
}
What I am trying is input the list, the following query
public List getListofStudentRecordsUsingList(List studentAssoc) {
Query query = new Query();
//for loop
for(StudentAssociation sa : studentAssoc) {
Criteria studentIdCriteria = Criteria
.where("studentId)
.is(studentId);
Criteria dateTimeCriteria = Criteria
.where(datetime)
.gte(sa.getAssocStarttime())
.lte(sa.getAssocEndtime());
query.addCriteria(studentIdCriteria);
query.addCriteria(dateTimeCriteria);
}
query.with(new Sort(Sort.Direction.ASC, datetime));
return mongoTemplate.find(query, Student.class);
}
Expected output is for each student in the list return list of documents and then merge the result for each student into a single list and sort based on time field to create a timeline
The error I am getting withe second query is
//this is solved when I moved the Query query = new Query(); inside the for Loop
InvalidMongoDbApiUsageException: Due to limitations of the com.mongodb.BasicDocument, you can't add a second 'studentId' criteria. Query already contains '{ "lStudentId" : "st1234" }' //this is resolved when I move the query inside the for loop
Your Query object definition is outside of loop, so the same object is used for your whole loop. At second pass, query variable already contains a 'studentId' criteria, as explained in your error.
Try to set query inside loop, or clear your object at start of loop.
//for loop
for(StudentAssociation sa : studentAssoc) {
Query query = new Query();
...
Query in a loop is such an expensive thing, i would recommend you to use and & in for reduce the number of queries.
Mongodb provides operator like :
The $and operator evaluates one or more expressions and returns true if all of the expressions are true or if evoked with no argument expressions. Otherwise, $and returns false.
The $in operator selects the documents where the value of a field equals any value in the specified array.
Query findStudent = new Query(Criteria.where("studentId").in('List of id')
.and('datetime').gte('you should filter min time')
.lte('max time'))
.with(new Sort(Sort.Direction.ASC, datetime));

Entity Framework with LINQ using CONTAINS in WHERE clause very slow with large integer list

I have a function that first queries a function in the database that returns a list of employees that a user is allowed to see. This run in a couple ms. Using this list I query the employee table to get all the employees that this user is allowed to see. When I run the generated sql in query analyzer it takes only a few milliseconds. When it runs from entity framework it is taking over 8 seconds.
Initially I had a list of allowedEmployees but then read http://mikeinba.blogspot.ca/2009/09/speeding-up-linq-contains-queries-with.html and tried using a HashSet instead but the performace is still really bad. How can I get its performance similar to what it is in sql query analyzer? What am I doing wrong?
I am using SQL 2008 and EF5.0
public IQueryable<Employee> FindAllByWithPermissions(int eID, Expression<Func<Employee, bool>> predicate)
{
if (predicate != null)
{
HashSet<int> allowedEmployees = SecurityRepository.GetPermissableEmployees(eID);
return DataContext.Set<Employee>().Where(predicate).Where(p => allowedEmployees.Contains(p.EmployeeID)).AsQueryable<Employee>(); ;
}
throw new ArgumentNullException("Predicate value must be passed to FindBy<T,TKey>.");
}
It seems that when it writes the sql to have the IN clause it takes a long time. There are a few thousand permissable employees but why does generating the sql for it take so long?
Return an IQueryable instead of a HashSet, and then join it with your Employee IQueryable.
var availableEmployees = SecurityRepository.GetPermissableEmployees(eID);
var allEmployees= DataContext.Set<Employee>();
query = from item in allEmployees.Where(predicate)
join t in availableEmployees on item.EmployeeID equals t.EmployeeID
select item;

NHibernate IQueryable doesn't seem to delay execution

I'm using NHibernate 3.2 and I have a repository method that looks like:
public IEnumerable<MyModel> GetActiveMyModel()
{
return from m in Session.Query<MyModel>()
where m.Active == true
select m;
}
Which works as expected. However, sometimes when I use this method I want to filter it further:
var models = MyRepository.GetActiveMyModel();
var filtered = from m in models
where m.ID < 100
select new { m.Name };
Which produces the same SQL as the first one and the second filter and select must be done after the fact. I thought the whole point in LINQ is that it formed an expression tree that was unravelled when it's needed and therefore the correct SQL for the job could be created, saving my database requests.
If not, it means all of my repository methods have to return exactly what is needed and I can't make use of LINQ further down the chain without taking a penalty.
Have I got this wrong?
Updated
In response to the comment below: I omitted the line where I iterate over the results, which causes the initial SQL to be run (WHERE Active = 1) and the second filter (ID < 100) is obviously done in .NET.
Also, If I replace the second chunk of code with
var models = MyRepository.GetActiveMyModel();
var filtered = from m in models
where m.Items.Count > 0
select new { m.Name };
It generates the initial SQL to retrieve the active records and then runs a separate SQL statement for each record to find out how many Items it has, rather than writing something like I'd expect:
SELECT Name
FROM MyModel m
WHERE Active = 1
AND (SELECT COUNT(*) FROM Items WHERE MyModelID = m.ID) > 0
You are returning IEnumerable<MyModel> from the method, which will cause in-memory evaluation from that point on, even if the underlying sequence is IQueryable<MyModel>.
If you want to allow code after GetActiveMyModel to add to the SQL query, return IQueryable<MyModel> instead.
You're running IEnumerable's extension method "Where" instead of IQueryable's. It will still evaluate lazily and give the same output, however it evaluates the IQueryable on entry and you're filtering the collection in memory instead of against the database.
When you later add an extra condition on another table (the count), it has to lazily fetch each and every one of the Items collections from the database since it has already evaluated the IQueryable before it knew about the condition.
(Yes, I would also like to be the extensive extension methods on IEnumerable to instead be virtual members, but, alas, they're not)

When using entity framework Where does this syntax return all records locally before doing the where

db.AdDetails.Where( u => u.OwnerGUID == CurrentUserProviderKey)
I have an adDetails table that has an OwnerGUID field.
I want to pull out only ad details that belong to the currenly logged in user.
My query does not show any where clauses in the SQL when I look at it in the debugger.
Can someone help me figure out what is wrong with my statement and if all rows in the table will be brought back then all 10K records put though a where on the webserver?
I am really new to this.
Using the Where extension method will filter down the results.
Queries in Entity Framweork are not executed until you iterate over them. If you do:
var query = db.Where(u => u.OwnerGUID == key);
This does not execute the query. When you do the following:
var list = list.ToList();
OR
foreach( var item in query) { ... }
That is when the query will be executed in SQL. The results should be filtered with your WHERE clause at this point.

Resources