Linq Multiple where based on different conditions - c#

In search page I have some options based on them search query must be different . I have wrote this :
int userId = Convert.ToInt32(HttpContext.User.Identity.GetUserId());
var followings = (from f in _context.Followers
where f.FollowersFollowerId == userId && f.FollowersIsAccept == true
select f.FollowersUserId).ToList();
int value;
if (spto.Page == 0)
{
var post = _context.Posts.AsNoTracking().Where(p => (followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true).Select(p => p).AsEnumerable();
if(spto.MinCost != null)
{
post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost).Select(p => p);
}
if (spto.MaxCost != null)
{
post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost).Select(p => p);
}
if (spto.TypeId != null)
{
post = post.Where(p => p.PostTypeId == spto.TypeId).Select(p => p);
}
if (spto.CityId != null)
{
post = post.Where(p => p.PostCityId == spto.CityId).Select(p => p);
}
if (spto.IsImmidiate != null)
{
post = post.Where(p => p.PostIsImmediate == true).Select(p => p);
}
var posts = post.Select(p => new
{
p.Id,
Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName
}).AsEnumerable().Take(15).Select(p => p).ToList();
if (posts.Count != 0)
return Ok(posts);
return NotFound();
In this case I have 6 Query that take time and the performance is low and code is too long . Is there any better way for writing better code ?

Short answer: if you don't do the ToList and AsEnumerable until the end, then you will only execute one query on your dbContext.
So keep everything IQueryable<...>, until you create List<...> posts:
var posts = post.Select(p => new
{
p.Id,
Image = p.PostsImages
.Select(i => i.PostImagesImage.ImageAddress)
.FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName,
})
.Take(15)
.ToList();
IQueryable and IEnumerable
For the reason why skipping all the ToList / AsEnumerable would help to improve performance, you need to be aware about the difference between an IEnumerable<...> and an IQueryable<...>.
IEnumerable
A object of a class that implements IEnumerable<...> represents the potential to enumerate over a sequence that the object can produce.
The object holds everything to produce the sequence. Once you ask for the sequence, it is your local process that will execute the code to produce the sequence.
At low level, you produce the sequence by using GetEnumerator and repeatedly call MoveNext. As long as MoveNext returns true, there is a next element in the sequence. You can access this next element using property Current.
Enumerating the sequence is done like this:
IEnumerable<Customer> customers = ...
using (IEnumarator<Customer> customerEnumerator = customers.GetEnumerator())
{
while (customerEnumerator.MoveNext())
{
// there is still a Customer in the sequence, fetch it and process it
Customer customer = customerEnumerator.Current;
ProcessCustomer(customer);
}
}
Well this is a lot of code, so the creators of C# invented foreach, which will do most of the code:
foreach (Customer customer in customers)
ProcessCustomer(customer);
Now that you know what code is behind the foreach, you might understand what happens in the first line of the foreach.
It is important to remember, that an IEnumerable<...> is meant to be processed by your local process. The IEnumerable<...> can call every method that your local process can call.
IQueryable
An object of a class that implements IQueryable<...> seems very much like an IEnumerable<...>, it also represents the potential to produce an enumerable sequence of similar object. The difference however, is that another process is supposed to provide the data.
For this, the IQueryable<...> object holds an Expression and a Provider. The Expression represents a formula to what data must be fetched in some generic format; the Provider knows who must provide the data (usually a database management system), and what language is used to communicate with this DBMS (usually SQL).
As long as you concatenate LINQ methods, or your own methods that only return IQueryable<...>, only the Expression is changed. No query is executed, the database is not contacted. Concatenating such statements is a fast method.
Only when you start enumerating, either at its lowest level using GetEnumerator / MoveNext / Current, or higher level using foreach, the Expression is sent to the Provider, who will translate it to SQL and fetch the data from the database. The returned data is represented as an enumerable sequence to the caller.
Be aware, that there are LINQ methods, that don't return IQueryable<TResult>, but a List<TResult>, TResult, a bool, or int, etc: ToList / FirstOrDefault / Any / Count / etc. Those methods will deep inside call GetEnumerator / MoveNext / Current`; so those are the methods that will fetch data from the database.
Back to your question
Database management systems are extremely optimized for handling data: fetching, ordering, filtering, etc. One of the slower parts of a database query is the transfer of the fetched data to your local process.
Hence it is wise to let the DBMS do as much database handling as possible, and only transfer the data to your local process that you actually plan to use.
So, try to avoid ToList, if your local process doesn't use the fetched data. In your case: you transfer the followings to your local process, only to transfer it back to the database in the IQueryable.Contains method.
Furthermore, (it depends a bit on the framework you are using), the AsEnumerable transfers data to your local process, so your local process has to do the filtering with the Where and the Contains.
Alas you forgot to give us a description of your requirements ("From all Posts, give me only those Posts that ..."), and it is a bit too much for me to analyze all your queries, but you gain most efficiency if you try to keep everything IQueryable<...> as long as possible.
There might be some problems with the Int.TryParse(...). Your provider probably will not know how to translate this into SQL. There are several solutions possible:
Apparently PostCost represents a number. Consider to store it as a number. If it is an amount (price or something, something with a limited number of decimals), consider to store it as a decimal.
If you really can't convince your project leaders that numbers should be stored as decimals, either search for a job where they make proper databases, or consider to create a stored procedure that converts the string that is in PostCost to a decimal / int.
if you will only use fifteen elements, use the IQueryable.Take(15), not the IEnumerable.Take(15).
Further optimizations:
int userId =
var followerUserIds = _context.Followers
.Where(follower => follower.FollowersFollowerId == userId
&& follower.FollowersIsAccept)
.Select(follower => follower.FollowersUserId);
In words: make the following IQueryable, but don't execute it yet: "From all Followers, keep only those Followers that are Accepted and have a FollowersFollowerId equal to userId. From the remaining Followers, take the FollowersUserId".
It seems that you only plan to use it if page is zero. Why create this query also if page not zero?
By the way, never use statements like where a == true, or even worse: if (a == true) then b == true else b == false, this gives readers the impression that you have difficulty to grasp the idea of Booleans, just use: where a and b = a.
Next you decide to create a query that zero or more Posts, and thought it would be a good idea to give it a singular noun as identifier: post.
var post = _context.Posts
.Where(post => (followings.Contains(post.PostsUserId)
|| post.PostsUser.UserIsPublic
|| post.PostsUserId == userId)
&& post.PostIsAccept);
Contains will cause a Join with the Followers table. It will probably be more efficient if you only join Accepted posts with the followers table. So first check on PostIsAccept and the other predicates before you decide to join:
.Where(post => post.PostIsAccept
&& (post.PostsUser.UserIsPublic || post.PostsUserId == userId
|| followings.Contains(post.PostsUserId));
All non-accepted Posts won't have to be joined with the Followings; depending on whether your Provider is smart enough: it won't join all public users, or the one with userId, because it knows that it will already pass the filter.
Consider to use a Contains, instead of Any
It seems to me that you want the following:
I have a UserId; Give me all Accepted Posts, that are either from this user, or that are from a public user, or that have an accepted follower
var posts = dbContext.Posts
.Were(post => post.IsAccepted
&& (post.PostsUser.UserIsPublic || post.PostsUserId == userId
|| dbContext.Followers
.Where(followers => ... // filter the followers as above)
.Any());
Be aware: I still haven't executed the query, I only have changed the Expression!
AFter this first definition of posts, you filter the posts further, depending on various values of spto. You could consider to make this one big query, but I think that won't speed up the process. It will only make it more unreadable.
Finally: why use:
.Select(post => post)
This doesn't do anything to your sequence, it will only make it slower.

Some observations:
.AsEnumerable()
This is meant to hide where operators if you use a custom collection. It should not be needed in this case.
.Select(p => p)
I fail to see any purpose for this, remove it.
int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost
Parsing can be expensive, so you want to do as little as possible, and this does it twice, of four times if you have both min and max. replace with direct compares with value, i.e. int.TryParse(p.PostCost, out value) && value >= spo.MinCost. I would also suggest have an explicit case when there is both a min and max cost to avoid parsing twice.
followings.Contains(p.PostsUserId)
Followings is a list, so it will search thru all items. Use a HashSet to speed up performance. I.e. Replace .ToList() with ToHashSet() when creating the followings list. HashSet uses a hash table to make Contains() a constant time operation rather than a linear operation.
Query order
You would want to order the checks to eliminate as many items as early as possible, and make simple, fast, checks before slower checks.
Merge where operators
A single where operator is in general faster than multiple calls.
Use plain loop
If you really need as high performance as possible it might be better to use regular loops. Linq is great for writing compact code, but performance is usually better with plain loops.
Profile
Whenever you talk about performance it is important to point out the importance of profiling. The comments above are reasonable places to start, but there might be some completely different things that takes time. The only way to know is to profile. That should also give a good indication about the improvements.

I have solved my problem with ternary operator :
var post = _context.Posts.AsNoTracking().Where(p =>
(followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true
&& (spto.MinCost != null ? int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost : 1 == 1)
&& (spto.MaxCost != null ? int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost : 1 == 1)
&& (spto.TypeId != null ? p.PostTypeId == spto.TypeId : 1 == 1)
&& (spto.CityId != null ? p.PostCityId == spto.CityId : 1 == 1)
&& (spto.IsImmidiate != null && spto.IsImmidiate == true ? p.PostIsImmediate == true : 1 == 1)).Select(p => new
{
p.Id,
Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName
}).Skip(spto.Page * 15).Take(15).ToList();
EDIT (Better Code) :
Thanx to #ZoharPeled , #HaraldCoppoolse , #JonasH I have Changed the Code like this :
int value;
var post = _context.Posts.AsNoTracking().Where(p =>
(followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true
&& (spto.MinCost == null || (int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost))
&& (spto.MaxCost == null || (int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost))
&& (spto.TypeId == null || p.PostTypeId == spto.TypeId)
&& (spto.CityId == null || p.PostCityId == spto.CityId)
&& (spto.IsImmidiate == null || p.PostIsImmediate == true)).Select(p => new
{
p.Id,
Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName
}).Skip(spto.Page * 15).Take(15).ToList();
Edit (Best Code) :
int userId = Convert.ToInt32(HttpContext.User.Identity.GetUserId());
var followings = _context.Followers
.Where(follower => follower.FollowersFollowerId == userId
&& follower.FollowersIsAccept)
.Select(follower => follower.FollowersUserId);
int value;
var post = _context.Posts.AsNoTracking().Where(p => p.PostIsAccept
&& (p.PostsUser.UserIsPublic || p.PostsUserId == userId
|| _context.Followers.Where(f => f.FollowersFollowerId == userId
&& f.FollowersIsAccept).Select(f => f.FollowersUserId).Any()));
if (spto.MinCost != null)
post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost);
if (spto.MaxCost != null)
post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost);
if (spto.TypeId != null)
post = post.Where(p => p.PostTypeId == spto.TypeId);
if (spto.CityId != null)
post = post.Where(p => p.PostCityId == spto.CityId);
if (spto.IsImmidiate != null)
post = post.Where(p => p.PostIsImmediate == true);
var posts = post.Select(p => new
{
p.Id,
Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName
}).Skip(spto.Page).Take(15).ToList();
if (posts.Count != 0)
return Ok(posts);

Related

Building efficient left anti-semi joins in EF6

I've been converting a long-standing, gnarly sproc into EF to get it into a more testable and maintainable position. Most of it has gone fairly well, except for this part here:
select p.idProduct from products p
where
// {some random filtering bringing us down to a manageable number of rows}
AND p.idProduct NOT IN (SELECT idProduct from productsShipped)
which I have converted into this:
var results = dbc.products.Where(p =>
p.warehouse == warehouse
&& p.BarConversion.Bar.BarDate > minDate
&& !dbc.productsShipped.Any(ps => ps.idInventory == p.idInventory)
//&& p.productsShipped == null
&& p.OPR.Order.Payment != null
&& !(p.OPR.Order.PaymentType == 5 &&
(p.OPR.Order.Payment.paymentStatus == null ||
p.OPR.Order.Payment.paymentStatus != "accepted"))
&& p.OPR.Order.OrderSla.expectedShipDate <= dueDateCutoff);
The issue I'm having is that the productsShipped table is absolutely enormous. In raw SQL, the where clause must understand that it does not need to pull the entirety of the productsShipped table and instead only fetches products which relate to the previous query. The EF equivalent breaks it down into a subquery and asks for every entry in the productsShipped table, causing the query to take more than five minutes, as opposed to the couple of seconds it takes to run without this filter. I've tried adding a relationship between the two entities with similar results.
Is there a way I can force Entity to make a proper left outer exclusive join rather than a subquery or similarly improve performance, or am I forced to either take this performance hit or push part of my logic into a difficult-to-test sproc?
Here is an extension method (from MSDN) for a left anti-semijoin:
public static IQueryable<TLeft> LeftAntiSemiJoin<TLeft, TRight>(this IQueryable<TLeft> left, IQueryable<TRight> right, Expression<Func<TLeft, TRight, bool>> predicate) {
var leftPrm = predicate.Parameters[0];
var rightPrm = predicate.Parameters[1];
// retrieve methods
var anyMethod = ((Func<IQueryable<TRight>, bool>)Queryable.Any).Method;
var whereMethod = ((Func<IQueryable<TRight>, Expression<Func<TRight, bool>>, IQueryable<TRight>>)Queryable.Where).Method;
// l => !right.Where(r => predicate(l, r)).Any()
var leftPredicate = Expression.Lambda<Func<TLeft, bool>>(
Expression.Not(
Expression.Call(anyMethod,
Expression.Call(whereMethod,
Expression.Constant(right),
Expression.Lambda<Func<TRight, bool>>(predicate.Body, rightPrm)))),
leftPrm);
return left.Where(leftPredicate);
}
Which you can use like this:
var results2 = dbc.products.LeftAntiSemiJoin(dbc.productsShipped, (p, ps) => p.idInventory == ps.idInventory)
.Where(p =>
p.warehouse == warehouse &&
p.BarConversion.Bar.BarDate > minDate &&
p.OPR.Order.Payment != null &&
!(p.OPR.Order.PaymentType == 5 &&
(p.OPR.Order.Payment.paymentStatus == null ||
p.OPR.Order.Payment.paymentStatus != "accepted")) &&
p.OPR.Order.OrderSla.expectedShipDate <= dueDateCutoff);
Perhaps it will be faster?
While I was ultimately unable to get Entity to generate the SQL the way I wanted, I found I was able to run a separate query to get the necessary data from productsShipped, drop that into a dictionary, and do the lookup from there.

Difference between these 2 LINQ queries using Azure mobile client

So I'm having issues with this LINQ query I had been using for some time, and now it seems to not be working as expected.
messagesWithoutConditional =
await
MobileServiceInstance.GetSyncTable<Messages>()
.OrderByDescending(key => key.SentDate)
.Take(50)
.Where(
p =>
(p.Uuid == myUuid && p.RecipientUuid == otherUuid) ||
(p.Uuid == otherUuid && p.RecipientUuid == myUuid))
.ToListAsync();
So lets say I have this query that simply returns the last 50 messages sent between 2 parties. Now if I want to add an additional condition that the 50 messages should also be before a certain date I would expect to do something like this
messagesWithConditional =
await
MobileServiceInstance.GetSyncTable<Messages>()
.OrderByDescending(key => key.SentDate)
.Take(50)
.Where(
p =>
((p.Uuid == myUuid && p.otherUuid == recipientUuid) ||
(p.Uuid == otherUuid && p.RecipientUuid == myUuid))
&& p.SentDate < 'some date')
.ToListAsync();
Lets suppose I should expect this to return 40 messages, but it returns 0. However, if I alternate the query into this
messagesWithConditional = messagesWithoutConditional.Where(p => p.SentDate < 'some date').ToList();
then I will receive the 40 expected messages by querying the result of my original expression.
How is the second approach any different from the first? Ideally I would like to use the && operator to add a new conditional expression rather than break off into a second where clause
Edit
I should also note that the times are in UTC, and the SentDate attribute is of type DateTimeOffset
As of right now it appears to be a bug, unless some other insight is made. Please refer to the bug report for more information
.Where should be put before the .OrderByDescending and .Take(50)

Most efficient (& fastest) way to query a list

I'm trying to work out the most performant way to query a list. I know that there are a ton of examples out there and this has come up loads before, but I'm really new to this and I'm struggling with how to apply some of the concepts to my situation.
private static void KeepMatchesBasedOnRestrictions(ref List<Entity> matches,
List<Entity> preFilteredShifts, List<Entity> locationalInformations)
{
if (matches.Count == 0) return;
matches.RemoveAll(
(match) => ( GeographyHasRestriction(match, preFilteredShifts, locationalInformations) )
);
}
private static bool GeographyHasRestriction(Entity match, List<Entity> preFilteredShifts, List<Entity> locationalInformations)
{
EntityReference fw = match.GetAttributeValue<EntityReference>("crm_fw");
Entity shift = preFilteredShifts.Single<Entity>(
a => match.GetAttributeValue<EntityReference>("crm_shift").Id == a.Id
);
EntityReference trust = shift.GetAttributeValue<EntityReference>("crm_trust");
EntityReference location = shift.GetAttributeValue<EntityReference>("crm_location");
EntityReference ward = shift.GetAttributeValue<EntityReference>("crm_ward");
Dictionary<Guid, Entity> locInfoRecs = locationalInformations.ToDictionary(p => p.Id);
var locationalInformationQuery = from loc in locationalInformations
where (
(
loc.GetAttributeValue<EntityReference>("crm_fw").Id == fw.Id
&& !loc.Contains("crm_trust")
&& !loc.Contains("crm_location")
&& !loc.Contains("crm_ward")
)
||
(
loc.GetAttributeValue<EntityReference>("crm_fw").Id == fw.Id
&& loc.GetAttributeValue<EntityReference>("crm_trust").Id == trust.Id
&& !loc.Contains("crm_location")
&& !loc.Contains("crm_ward")
)
||
(
loc.GetAttributeValue<EntityReference>("crm_fw").Id == fw.Id
&& loc.GetAttributeValue<EntityReference>("crm_trust").Id == trust.Id
&& loc.GetAttributeValue<EntityReference>("crm_location").Id == location.Id
&& !loc.Contains("crm_ward")
)
||
(
loc.GetAttributeValue<EntityReference>("crm_fw").Id == fw.Id
&& loc.GetAttributeValue<EntityReference>("crm_trust").Id == trust.Id
&& loc.GetAttributeValue<EntityReference>("crm_location").Id == location.Id
&& loc.GetAttributeValue<EntityReference>("crm_ward").Id == ward.Id
)
)
select loc;
foreach (Entity loc in locationalInformationQuery)
{
if (loc.GetAttributeValue<bool>("crm_hasrestriction"))
{
return true;
}
}
//return false;
}
So I think my problem is 2-fold;
The locationalInformationQuery query seems to run very slowly... I'm talking something in the region of up to 2 seconds per iteration which is horrible.
I also suspect that the approach of calling matches.RemoveAll() is also somewhat flawed due to the performance issues regarding lists.
So in terms of addressing this, I think that I may be able to get better performance by converting my locationalInformations list to some other type of container such as a Dictionary, HashSet or SortedList. My problem then is that I have no idea how to go about adjusting my query to to take advantage of those more efficient containers.
As far as the second point goes, I'd also be curious to hear about alternatives to using List.RemoveAll(). I have the flexibility to modify my incoming container types within reason to this may be viable.
With regards the list sizes in case its of any use, match contains a few thousand items and preFilteredShifts and locationalInformations each contain > 100,000 items.
As an aside I've tried using Parallel.ForEach instead of foreach, but it made virtually no difference whatsoever.
Edit: Just to clarify some questions, I'm doing all this in memory. I've already completely populated all of my lists so there shouldn't be any additional round trips to the DB. I'm reasonably certain that GetAttributeValue<EntityReference> doesn't initial further DB overhead.
Also, yes this is a local application calling Dynamics CRM Online.
The code -
foreach (Entity loc in locationalInformationQuery)
{
if (loc.GetAttributeValue<bool>("crm_hasrestriction"))
{
return true;
}
}
Can be one reason for slowness. You are fetching more data and then enumerating them in memory. You can perform the check directly before fetch, so you will fetch lesser data and could be faster. Something like this -
return (from loc in locationalInformations
where ((
(
loc.GetAttributeValue<EntityReference>("crm_fw").Id == fw.Id
&& !loc.Contains("crm_trust")
&& !loc.Contains("crm_location")
&& !loc.Contains("crm_ward")
)
||
(
loc.GetAttributeValue<EntityReference>("crm_fw").Id == fw.Id
&& loc.GetAttributeValue<EntityReference>("crm_trust").Id == trust.Id
&& !loc.Contains("crm_location")
&& !loc.Contains("crm_ward")
)
||
(
loc.GetAttributeValue<EntityReference>("crm_fw").Id == fw.Id
&& loc.GetAttributeValue<EntityReference>("crm_trust").Id == trust.Id
&& loc.GetAttributeValue<EntityReference>("crm_location").Id == location.Id
&& !loc.Contains("crm_ward")
)
||
(
loc.GetAttributeValue<EntityReference>("crm_fw").Id == fw.Id
&& loc.GetAttributeValue<EntityReference>("crm_trust").Id == trust.Id
&& loc.GetAttributeValue<EntityReference>("crm_location").Id == location.Id
&& loc.GetAttributeValue<EntityReference>("crm_ward").Id == ward.Id
)
) && loc.GetAttributeValue<bool>("crm_hasrestriction")) // do the check before fetch in here
select loc).Any();
I have on occasion found that querying CRM databases can result in inefficient queries when what the query you are working with is sufficiently complex.
Sometimes this can be due to how the query is generated depending on your method of querying the database, or sometimes it can be that iterating some IEnumerable type collections and checking conditions can result in many SQL queries to the database per iteration. Maybe check what is happening under the covers against your database by using SQL Profiler. It may turn out to be insightful.
One option I've reverted to on occasion where I feel the CRM query limitations simply hamper performance too much is to fall back to straight ADO.NET and SQL against the filtered views where I have access to query plans and have a much better idea and understanding of what is happening. I'm sure many CRM purists are frowning at me right now, but I think it is a fair call in terms of the end users experience and also making your code relatively understandable too. Complex queries can be quite unwieldy in code, and having a SQL query you can refer to, can help immensely in comprehension of your solution. You can also benefit from set based operations and a less "chatty" interface in terms of the number of resultant database calls.
In your question above, if you feel this may be a good option, I'd look at prototyping such a solution by providing a method like ;
private static bool GeographyHasRestrictionBySql(Entity match, List<Entity> preFilteredShifts, List<Entity> locationalInformations)
{
// Query here, and determine your boolean result to return
}
That way you can simply test this quickly and easily by a change in the calling function.

Can a conditional LINQ query be combined into one that runs every time?

I've got two queries used to filter down a list of data. The main part runs every time, and in normal situations should cut the total number of results down to at most a dozen or two. When I'm editing a Foo object instead of creating a new one, I also want to remove the copy saved in the DB from the set of data I'm working on.
I'm currently using an if statement and a second query to strip it out. Is there a way to combine the condition and second query into the first one?
IEnumerable<Foo> myFoos = bar.GetFoos()
.Where(f => f.Value1 == value1 && f.Value2 == value2);
if (editFoo != null)
myFoos = myFoos.Where(f => f.ID != editFoo.ID);
When I try the queries suggested by sehe or Dan Seaver, and call .ToList() on myFoos I get an exception:
Unable to create a constant value of type 'Foo'. Only primitive types ('such as Int32, String, and Guid') are supported in this context
I created dummy objects that contained every property in my un-munged query and it ran without error, so I'm leaning towards suspecting the problem is that the Entity Framework doesn't understand the null checks in the proposed queries and is barfing on them. The entity framework was to blame; without it either solution would work as written.
Boolean logic to the rescue.
IEnumerable<Foo> myFoos = bar.GetFoos()
.Where(f =>
(f.Value1 == value1) &&
(f.Value2 == value2) &&
((editFoo == null) || (f.ID != editFoo.ID)));
The thing that makes it work is shortcut evaluation
You could do something like:
IEnumerable<Foo> myFoos = bar.GetFoos()
.Where(f => f.Value1 == value1 &&
f.Value2 == value2 &&
((editFoo != null) ? (f.ID != editFoo.ID) : true) );

Possible to do this in linq?

I am wondering if something like this could be done(of course what I have written does not work but that's what I am essentially trying to achieve) .
var test = u.Owner;
a.Table.Where(u => test == true)
I have a linq query that I want to reuse(basically I got a query that I use 5 times) and the only thing that changes is what I am comparing against.
So in the above u.Owner is compared against true. In another query it would look the same but instead of u.Owner I might have u.Add == true or u.Edd == true.
So is there a way I can sort of reuse what I have. Of course in my code the query is a bit longer but I just shortened down.
Edit
Basically the whole query
List<Permission> clearence = user.PermissionLevels.Where(u => u.Id == Id &&( u.Add == permissionNeeded || u.Permission.Name == PermissionTypes.Owner)).ToList();
permissionNeeded == Enum
So my orignal way of doing it was u.Permission.Name == permissionNeeded so I compared the enum value to a string.
Now my db model has change and I need to check against 5 different permissions separately that are bools and not strings.
u.Add = true || u.Owner == true;
u.Edit = true || u.Owner == true;
u.Delete= true || u.Owner == true;
u.View= true || u.Owner == true;
Thats all changes for the entire query so that's why I am trying to make it into one query(just like I had it before).
So I am thinking of having a switch statement. The method still takes in a permissionNeeded(enum) I then go through and determine what clause I need and some how insert it into the query.
switch(PermssionNeeded)
{
case PermissionTypes.Add:
u.Add;
break;
// all other cases here.
}
Take advantage of the fact that Where can have any function taking type Table as a parameter and returning a boolean to create a function like:
public IQueryable<Table> QueryTables(Func<Table, bool> testFunction)
{
return a.Table.Where(testFunction).AsQueryable<Table>();
}
Edit: (in addition to edit to add AsQueryable above, which I earlier forgot)
If all you want to do is vary the boolean field used in the test, and you don't want to have to specify an entire function (which you're about to find out is much easier), you would need to use some reflection:
using System.Reflection;
public IQueryable<Table> QueryTables(PropertyInfo pi)
{
return a.Table.Where(t => (bool)(pi.GetGetMethod().Invoke(t, null))).AsQueryable<Table>();
}
To construct the PropertyInfo object, use something like:
PropertyInfo pi = typeof(Table).GetProperty("Owner");
I prefer the earlier method, but I did want to show that something like this is at least possible.
If you only want to specify the property you are checking you can do
public IEnumerable<Table> GetTables(Func<Table,bool> getValue)
{
return a.Table.Where(table => /*some common filter*/)
.Where(table => getValue(table))
}
var query = from u in uSrc join v in vSrc on u.ID equals v.RelatedUID
where v.Valid && u.Created < DateTime.UtcNow.AddDays(-365)
select u; // relatively complicated starting point.
var q1 = query.Where(u => u.Add); // must also have Add true
var q2 = query.Where(u => u.Test); // must also have Test true
var q3 = query.Where(u => u.ID < 50); // must also have ID < 50
And so on.
Edit:
Okay, so your starting query is:
List<Permission> clearence = student.PermissionLevels.Where(u => u.Id == Id &&( u.Add == permissionNeeded || u.Permission.Name == PermissionTypes.Owner)).ToList();
However, note that this creates a list, so any further work done on it will be a matter of Linq-to-objects. We'll come back to that in a minute, as it's sometimes good and sometimes not.
Now, if I understand you correctly, you need different sets for different cases, which you can do with your query as per:
var set0 = clearance.Where(u.Add = true || u.Owner == true);
var set1 = clearance.Where(u.Edit = true || u.Owner == true);
var set2 = clearance.Where(u.Delete= true || u.Owner == true);
var set3 = clearance.Where(u.View= true || u.Owner == true);
Now, this will work, but may not be the best approach. If we go back to the original query, we don't have to do ToList(), but can have:
IQueryable<Permission> clearence = student.PermissionLevels.Where(u => u.Id == Id &&( u.Add == permissionNeeded || u.Permission.Name == PermissionTypes.Owner));
Now, in the first case because we built a list we first got all values that matched the critera backed, and then stored it in memory, in clearance.
In the second case, clearance doesn't store any values at all, but instructions on how to get them.
The question is which is better to use. In the case where we are going to end up using the vast majority of the objects returned by the first query on its own, then there is a performance boost in using the list version, because they are loaded into memory only once, and then taken from memory without hitting the database again.
However, in most cases, it's better to do the second version for two reasons:
We hit the database in each case, but only retrieve the objects needed in that case.
We don't store anything in memory longer than necessary. Above a certain amount this is an important performance matter in itself.
The time to first item is faster this way.
We can further refine, for example if we do the following:
var trimmed = from set0 select new{u.Id, u.Permission.Name};
The we retrieve anonymous objects with Id and Name properties that are all we care about for a particular case, and not all of the relevant fields are retrieved from the database, or other source.
I've recently come to prefer a Dictionary over switch statements. You could store all your lambas in a Dictionary<PermissionNeeded, Func<User, bool>> that would look like this:
Dictionary<PermissionNeeded, Func<User, bool>> Permissions =
new Dictionary<PermissionNeeded, Func<User, bool>> {
{ PermissionNeeded.Add, u => u.Add }, // don't need to specify == true
{ PermissionNeeded.Edit, u => u.Edit },
...
etc
};
And you would call it like this:
var clearance = Permissions[PermissionNeeded.Add](user);
or maybe
var clearance = Permissions[PermissionNeeded.Add](user) && Permissions[PermissionNeeded.Edit](user);
or perhaps
var clearance = Permission[PermissionNeeded.Add](user) || Permissions[PermissionNeeded.View](user);
and so on. Even if you don't have a user object, I think this would still be valid and make the code pretty easy to read, and if you have to modify your functions, its all in the dictionary...

Categories