Is there a nicer way to write this linq query? - c#

I have a list of Asset entities, and each Asset has a list of Field entities with two properties each that looks like this
| Index | Value |
| 0 | "hello" |
| 1 | "blah" |
| 2 | null |
and in a loop I get variables
i = 2 and i = 3
and I have a linq query to try and get the following: Assets that have a Field where the Value corresponding to i is null, or there is no Field with an Index i.
For example, if i is 2, it will return the asset that has the table above, because it has a Field where 2 corresponds to null.
And, if i is 3, it should also return the above because there is no Field with Index 3.
This code works:
var assets = (from a in assets where
a.Fields.Any(x => x.Index == i && x.Value == null) select a)
.Union(from a in assets where
a.Fields.All(x => x.Index != i) select a)
.ToList();
This isn't very nice, and I'm wondering is there a way to do it in one statement?

The other answers work, but if you simplify the question it gets even more straightforward:
assets.Where(a => !a.Fields().Any(f => f.Index == i &&
f.Value != null))
.ToList()
You want all the Assets where there isn't a Field with an Index of i and a non-null Value. You don't need to split that into two conditions.

You don't need two queries, just use one with an ||:
assets = assets
.Where(a => a.Fields.Any(f => f.Index == i && f.Value == null)
|| a.Fields.All(f => f.Index != i))
.ToList();

You have a few options, as I see it:
use a more traditional JOIN syntax of LINQ and LEFT JOIN only on the Asset ID and Index. So if the LEFT JOIN returns null (i.e. DefaultIfEmpty()) then that index wasn't found for that Asset. That's case #2. However, if it is NOT null, then you can filter (i.e. where clause) further by checking that Value is null. That's case #1.
You can combine where statement. where a.Fields.Any(...) || a.Fields.All.
You would want to try them both, in my opinion, to see which performs better for your needs. I'd guess the first option would perform much better, but if the data size is small, the second option is definitely much easier.

var assets = assets.where(a => a.Fields.Any(x => x.Index == i && x.Value == null) || a.Fields.All(x => x.Index != i)).ToList();

You can actually shortcut the query a bit in this case
var assets = (
from a in assets
where a.Fields.All(x => x.Index != i || x.Value == null)
select a
).ToList();

Related

How to use temp local data when creating Anonymous type?

I'm getting some Entities from EF, which I iterate and creating Anonymous type objects such as:
var payments = ctx.Payments.ToList();
var data = ctx.Activities.OrderBy(p => p.ID).ToList().Select(p => new
{
ID = p.ID,
Date = p.Date?.ToString("dd/MM/yyyy"),
PaymentMethod = p.PaymentMethods != null ? p.PaymentMethods.Description : "",
ActivityStatusID = payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() == 0 ? 1 : .. // I need some other check
}).ToList();
Now, I'd like to check the payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() several times before set a value.
Such as:
if .Count() == 0, value 1
if .Count() == 1, value 8
if .Count() > 1, value 10
else 87
and so on. I won't do somethings like this:
ActivityStatusID = payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() == 0 ? 1 : payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() == 1 ? 8 : payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() > 1 ? 10 : 87
Is there any way to do payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count() once for each p, and than evalutate the conditions?
People suggested to convert your query into query syntax, which would enable your to use a let statement to create a variable in which you could save count.
If you investigate what let does, it adds one column value to your result. Every row in your result has the same let value in this column. See this answer on stackoverflow
Keep your query in Method Syntax
If you want to keep your query in method syntax, simply add a Count to your anonymous type:
var result = ctx.Activities
.OrderBy(p => p.ID)
.Select(p => new
{
Id = p.Id,
Date = p.Date?.ToString("dd/MM/yyyy"),
PaymentMethod = p.PaymentMethods != null ? p.PaymentMethods.Description : "",
PaidCount = payments
.Where(q => q.ActivityID == p.ID && !q.Paid)
.Count();
})
.Select(p => new
{
Id = p.Id,
Date = p.Date,
ActivityStatusId =
{
// count == 0 => 1
// count == 1 => 8
// count > 1 => 10
// count < 0 => 87
if (p.PaidCount < 0) return 87;
switch (p.PaidCount)
{
case 0:
return 0;
case 1:
return 8;
default:
return 10;
}
},
});
Note, the switch is only possible because you brought your complete ctx.Activities to local memory using the ToList.
Improved efficiency
You do a ToList before your select. This means that your complete ctx.payments are materialized in local memory into a List, before you start selecting and counting items in your sequence.
If ctx.Payments is from an external source, like a database, or a file, then ctx.Payments is an IQueryable instead of an IEnumerable. Fetching your complete Payments to local memory is not an efficient approach.
Advise: Whenever you have an IQueryable, try to keep it IQueryable as long as possible. Your source data provider can process your queries much more efficiently than your local processor. Only materialize it to local memory if your source data provider can't process it anymore, for instance because you need to call local procedures, or because there is nothing to process anymore.
Furthermore, don't move values to local memory that you don't plan to use. Only Select the properties that you actually will use in your local memory.
One improvement would be:
var result = ctx.Payments.Select(payment => new
{ // select only the properties you plan to use locally:
Id = payment.Id,
Date = payment.Date,
PaymentMethod = payment.PaymentMethods?.Description,
PaidCount = ctx.Payments
.Where(q => q.ActivityID == p.ID && !q.Paid)
.Count(),
})
.OrderBy(fetchedPaymentData => fetchedPaymentData.Id)
// from here you need to move it to local memory
// Use AsEnumerable instead of ToList
.AsEnumerable()
.Select(fetchedPaymentData => new
{
Id = fetchedPaymentData.Id,
PaymentMethod = fetchedPaymentData.PaymentMethod ?? String.Empty,
ActivityStatusId = {...}
});
AsEnumerable is more efficient than ToList, especially if you don't need all items at once. For instance if you would end with FirstOrDefault, or only Take(5), then it would be a waste to move all items to local memory.
Finally: with some trying you can get rid of the switch statement, thus allowing your DBMS to calculate the ActivityStatusId. But as the selecting of your source data and the transport of the selected data to local memory is the slower part of your complete query, I doubt whether this would lead to shorter execution time. The switch surely makes your requirement better readable, especially if your numbers 1 / 8 / 87 are put into enums.
I would change the query from a lambda expression to an ordinary query. Then use the let syntax to set a variable for each iteration. Something like this:
var payments = ctx.Payments.ToList();
var data = (
from p in ctx.Activities.ToList()
orderby p.ID
let paymentCount = payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count()
select new
{
ID = p.ID,
Date = p.Date?.ToString("dd/MM/yyyy"),
PaymentMethod = p.PaymentMethods != null ? p.PaymentMethods.Description : "",
ActivityStatusID = paymentCount == 0 ? 1 : .. // I need some other check
}
).ToList();
And btw you can do this part diffrently as well. This:
payments.Where(q => q.ActivityID == p.ID && !q.Paid).Count()
You can write like this:
payments.Count(q => q.ActivityID == p.ID && !q.Paid)

LINQ subquery with multiple columns

I'm trying to recreate this SQL query in LINQ:
SELECT *
FROM Policies
WHERE PolicyID IN(SELECT PolicyID
FROM PolicyRegister
WHERE PolicyRegister.StaffNumber = #CurrentUserStaffNo
AND ( PolicyRegister.IsPolicyAccepted = 0
OR PolicyRegister.IsPolicyAccepted IS NULL ))
Relationship Diagram for the two tables:
Here is my attempt so far:
var staffNumber = GetStaffNumber();
var policyRegisterIds = db.PolicyRegisters
.Where(pr => pr.StaffNumber == staffNumber && (pr.IsPolicyAccepted == false || pr.IsPolicyAccepted == null))
.Select(pr => pr.PolicyID)
.ToList();
var policies = db.Policies.Where(p => p.PolicyID.//Appears in PolicyRegisterIdsList)
I think I'm close, will probably make two lists and use Intersect() somehow but I looked at my code this morning and thought there has to be an easier way to do this,. LINQ is supposed to be a more readble database language right?
Any help provided is greatly appreciated.
Just use Contains:
var policies = db.Policies.Where(p => policyRegisterIds.Contains(p.PolicyID));
Also better store policyRegisterIds as a HashSet<T> instead of a list for search in O(1) instead of O(n) of List<T>:
var policyRegisterIds = new HashSet<IdType>(db.PolicyRegisters......);
But better still is to remove the ToList() and let it all happen as one query in database:
var policyRegisterIds = db.PolicyRegisters.Where(pr => pr.StaffNumber == staffNumber &&
(pr.IsPolicyAccepted == false || pr.IsPolicyAccepted == null));
var policies = db.Policies.Where(p => policyRegisterIds.Any(pr => pr.PolicyID == p.PolicyID));

Successive SelectMany in Linq Request

I have three tables built with EF code first.
I try to retrieve some information with SelectMany so that I can flatten the query and get only the fields that I need among those three tables.
My tables are presented as follow:
Tables: ProductOptions *-* ProductOptionValues 1-* LanguageProductOptionValue
|ProductOptionID | OVPriceOffset | LanguagesListID
|PriceOffset | OptionValueCategory | ProductOptionValueName
| | ... |
var queryCabColor = _db.ProductOptions
.Where(c => c.ProductOptionTypeID == 18 && c.ProductId == 1)
.SelectMany(z => z.ProductOptionValues, (productOptions, productOptionValues)
=> new
{
productOptions.ProductOptionID,
productOptions.PriceOffset,
productOptionValues.OVPriceOffset,
productOptionValues.OptionValueCategory,
productOptionValues.ProductOptionValuesID,
productOptionValues.Value,
productOptionValues.LanguageProductOptionValue
})
.SelectMany(d => d.LanguageProductOptionValue, (productOptionValues, productOptionValuesTranslation)
=> new
{
productOptionValuesTranslation.LanguagesListID,
productOptionValuesTranslation.ProductOptionValueName
})
.Where(y => y.LanguagesListID == currentCulture);
So far, when I loop in the query I can just retrieve the LanguagesListID and ProductOptionValueName and I can't find a way to get all of the above mentionned fields. Any suggestion?
I think in your case the Linq syntax is more appropriate than explicit SelectMany. Something like this should work:
var queryCabColor =
from productOptions in db.ProductOptions
where productOptions.ProductOptionTypeID == 18 && productOptions.ProductId == 1
from productOptionValues in productOptions.ProductOptionValues
from productOptionValuesTranslation in productOptionValues.LanguageProductOptionValue
where productOptionValuesTranslation.LanguagesListID == currentCulture
select new
{
productOptions.ProductOptionID,
productOptions.PriceOffset,
productOptionValues.OVPriceOffset,
productOptionValues.OptionValueCategory,
productOptionValues.ProductOptionValuesID,
productOptionValues.Value,
productOptionValuesTranslation.LanguagesListID,
productOptionValuesTranslation.ProductOptionValueName
};

Why do the 2 LINQ queries get evaluated differently

Query 1
var resulty = db.QIS
.Where(w=>w.QSA.Any(a => a.QSID != w.QSID))
.Select(s => s.QSID).ToList();
Query 2
var resultz = db.QIS
.Where(w=>w.QSA.Where(h => h.QSID == w.QSID).Select(s => s.QSID).FirstOrDefault() != w.QSID)
.Select(s => s.QSID).ToList();
Table QIS and QSA are related Many:Many. The idea here is to find how many QIS.ID's are not Found in QSA where QIS.QID == QSA.QID.
Query 1 returns 0
Query 2 on the other hand gives me what I expected to see ( the list off all non matching QIS records.)
Why will the any not work? - i found myself running into the same situation a couple of times now in seperate scenarios... thanks for any help / thoughts.
PS: Prefer method syntax.
In the filtering in your second version, will only be true if the inner Where clause returns no elements, so that FirstOrDefault() returns null or 0 (depending on if the type is nullable or not).
w=>w.QSA.Where(h => h.QSID == w.QSID)
.Select(s => s.QSID).FirstOrDefault() != w.QSID
Which is equivalent to (now assuming QSID is a non nullable numeric type, if it is nullable, use null instead of zero):
w=>w.QSA.Where(h => h.QSID == w.QSID)
.Select(s => s.QSID).FirstOrDefault() == 0
which can be rewritten to:
w=>w.QSA.Where(h => h.QSID == w.QSID).FirstOrDefault() == null
which can be rewritten to:
w=>!w.QSA.Any(h => h.QSID == w.QSID)
which is nearly the same as your initial version, but not exactly. You still want to check for equivalence inside the Any() filter, but then negate the result.

How do I do this in Entity Framework (multiple where's or Join)?

I have 2 tables that have a relation ship to each other
Table A has 1 to many relationship with table B, so this creates a navigation property into each.
Now I need to check a value from Table A (userName) and I need to check a value from table B (ClubId).
So in my mind it would be something like
Join the tables together
Where A.userName == "bob" &&
where B.clubId == "Car"
// return the count.
but now I know with Entity stuff it should make joins less common so I am wondering if I can do it with a join then.
I tried this
int count = Entity.TableA.where(a => a.userName == "bob" && a.TableB.where(i => i.ClubId == "Car")).Count();
so this does not work since it won't return the right type(the 2nd where). This is just how I thought along the lines how I would expect it to be done would work.
So how should it look?
P.S
I rather have an example done in the Linq method queries like I did above.
Assuming your EF model has the relationship between Users and Clubs could do something like this:
var usersNamedBobInCarClub =
from A in User
from B in A.Clubs
where A.userName == "bob" &&
B.clubId == "Car"
select A;
If you want return elements of both Users and Clubs have a look at joins within the query.
Filtering TableA before your join is probably more efficient:
var clubs = from a in Entity.TableA
where a.userName == "bob"
from b in a.TableB
where b.clubId == "Car"
select b;
var count = clubs.Count();
You don't have to use two variables, it's just my preference here for clarity.
Or in method syntax you can simplify a bit:
var count = Entity.TableA.Where(a => a.userName == "bob")
.SelectMany(a => a.TableB)
.Count(b => b.clubId == "Car");
However, I'm not certain EF understands those particular expressions. If not, the compiler would translate the above query like this:
var count = Entity.TableA.Where(a => a.userName == "bob")
.SelectMany(a => a.TableB, (a,b) => new { a, b })
.Where(x => x.b.clubId == "Car")
.Count();

Categories

Resources