LINQ query operator for log table - linq

I am trying to find the corerct LINQ to SQL query operator and predicate combo that can operate on an audit table.
Imagine a table called Setting that has three columns : rowID, DefID, and Value.
I want to be able to check that every DefID ( in this case all definition 1 through 3 ) has at least one row which has a value set to true.
the LINQ expression should return a bool true or false. For example,
RowID DefID Value
1 1 true
2 2 false
3 3 true
LINQ returns false because DefID = 2 does not have any value = true
RowID DefID Value
1 1 true
2 2 false
3 2 true
returns false because defid 3 is missing
RowID DefID Value
1 1 true
2 1 false
3 2 true
4 3 true
returns true because ALL definitions have at least one value = true

Here is an example using extension methods:
int[] ids = new int[] { 1, 2, 3 };
bool allFound = Settings.Where( s => s.Value && ids.Contains( s.DefID ) )
.Select( s => s.DefID )
.Distinct()
.Count() == ids.Length;

I've never used linq to sql, but the linq to objects would look something like this:
defIds.All(d => d.rows.Any( row => row.Value == true ) )
To do it in raw SQL, I don't think it's possible in one query. You could do something like this though:
select id from DefIds
join rows on row.DefId = DefIds.ID
where row.Value = true
That would give you a list of defId's which have true values. In code you could then do something like
DefIds.Select(d => d.id).ToArray() == (results from select).ToArray()

Well, there are a lot of valid ways to do this. One simple way is:
int[] DefIDs = new int[] {1, 2, 3};
bool bHasValidDefs =
(
from set in myDataBase.settings
where
set.Value == true
&& DefIDs.Contains(set.DefID)
select set.DefID
).Distinct().Count() == DefIDs.Count();
This gets the number of unique DefIDs that are in your valid list and also have at least one "value == true" row. It then makes sure that the number of these valid DefIDs is equal to the expected value that you define above.

If you are ok with having a ValidSettings or SettingsMaster table, you could:
bool allFound = myContext.SettingsMaster.All(m=> m.Settings.Any(s=>s.Value));
For the current tables version, I would
int[] allIds = new int[] {1, 2, 3};
var validIds = (
from s in myContext.Settings
where s.Value
select s.DefId
)
.Distinct().ToList();
bool allFound = allIds.All(id => validIds.Contains(id));

Related

linq to entity,How does the where clause use ?: Expression

TAB_XXX and TAB_XXX_details are one-to-many relationships, I need to query the two tables, however, we need to be filtered TAB_XXX_details。
The code is as follows:
var qu = from c in db.TAB_XXX.Where(n => n.DELETE_MARK == false)
let dets = c.TAB_XXX_DETAILS.Where(n => condition.SaleType.HasValue ? n.SALE_TYPE == (decimal)condition.SaleType : 1 == 1)
select new
{
c,
dets
};
Condition.SaleType is number?, if the condition.SaleType is a valid number, such as 1, 2, 3 ... I want to filter the child record based on these numbers; when the condition.SaleType is null, I want to query TAB_XXX and all its child records;
How do I modify the where clause?
Thank you for your answer!
Since 1 == 1 is always true, your condition boils down to this:
let dets = c.TAB_XXX_DETAILS
.Where(n => !condition.SaleType.HasValue || n.SALE_TYPE == condition.SaleType.Value)
Essentially, you want to return all rows when condition.SaleType does not have value; otherwise, you make a comparison to condition.SaleType.Value.

LINQ get Max value from list

I have the following table:
ID Amt Received
-- ---- --------
2 55 N
2 88 Y
2 44 N
3 5 N
3 9 N
4 5 N
5 33 Y
6 43 N
7 54 N
var result = (from rs in db.Exp
where rs.ID == id
&& rs.Received == true
select rs).Max().Any();
Given an ID, I need to find the max Amt for a given id and then check if it is Y, if so, return true else return false.
This should do it;
db.Exp.
Where(x => x.ID == id).
OrderByDescending(x => x.Amt).
Take(1).
Any(x => x.Received == "Y");
Unfortunately LINQ doesn't provide a "max by an attribute" method. MoreLINQ does with its MaxBy operator, but that can't be translated into SQL of course. So if this is a LINQ to SQL (or whatever) query, you'll need a different approach. If it's already LINQ to Objects, however:
return db.Exp.Where(rs => rs.ID == id)
.MaxBy(rs => rs.Amt)
.Received;
Note that this is doing what the words of your question ask:
Out of the records with the given ID...
Find the one with the highest amount...
And check the value of Received
This is not the same as:
Out of the records with the given ID where received is true...
Find the one with the highest amount
Also note that this will throw an exception if there are no records with that ID.
If you want to do it in LINQ to SQL etc, you'd probably be best off with an ordering:
var highest = db.Exp.Where(rs => rs.ID == id)
.OrderByDescending(rs => rs.Amt)
.FirstOrDefault();
return highest != null && highest.Received;
You don't want to do this if you're using LINQ to Objects, as it will order all the results, when you only want to find the result with the highest amount.
You need to tell it what you want the Max of.
var result =
(from rs in db.Exp
where rs.ID == id && rs.Received
select rs)
.Max(row => row.Amt) == Y;
And you don't need the .Any() at all
// "I need to find the max Amt for a given id..."
var elementWithMaxAmount =
db.Exp
.Where(x => x.ID == id)
.OrderByDescending(x => x.Amount)
.FirstOrDefault();
// "... and then check if it is Y"
var result = (elementWithMaxAmount != null &&
elementWithMaxAmount.Received == "Y");

How to get two sums from a linq query

Summary
I have a list of Transactions. Using Linq, I want to get a sum of the Cost and sum of the Quantity from this list in one query.
Grouping
My first thought is to use grouping - but I don't really have a key that I want to group on, I want just one group with the results from the whole list. So, I happen to have a property called "Parent" that will be the same for all of the transactions, so I'm using that to group on:
var totalCostQuery =
(from t in Transactions
where t.Status != GeneralStoreTransactionStatus.Inactive &&
(t.Type == GeneralStoreTransactionType.Purchase ||
t.Type == GeneralStoreTransactionType.Adjustment)
group t by t.Parent into g
select new
{
TotalCost = g.Sum(t => t.Cost.GetValueOrDefault()),
TotalQuantity = g.Sum(t => t.Quantity.GetValueOrDefault())
});
Grouping by t.Parent seems like it could be wrong. I really don't want to group at all, I just want the sum of t.Quantity and sum of t.Cost.
Is that the correct way to get a sum of two different properties or can it be done in a different way.
Assuming this is Linq to SQL or Entity Framework, you can do that:
var totalCostQuery =
(from t in Transactions
where t.Status != GeneralStoreTransactionStatus.Inactive &&
(t.Type == GeneralStoreTransactionType.Purchase ||
t.Type == GeneralStoreTransactionType.Adjustment)
group t by 1 into g
select new
{
TotalCost = g.Sum(t => t.Cost),
TotalQuantity = g.Sum(t => t.Quantity)
});
Note that you don't need to use GetValueOrDefault, null values will be ignored in the sum.
EDIT: not sure this works with Linq to NHibernate though...
Note that if you're using Linq to objects, the solution above won't be efficient, because it will enumerate each group twice (once for each sum). In that case you can use Aggregate instead:
var transactions =
from t in Transactions
where t.Status != GeneralStoreTransactionStatus.Inactive &&
(t.Type == GeneralStoreTransactionType.Purchase ||
t.Type == GeneralStoreTransactionType.Adjustment)
select t;
var total =
transactions.Aggregate(
new { TotalCost = 0.0, TotalQuantity = 0 },
(acc, t) =>
{
TotalCost = acc.TotalCost + t.Cost.GetValueOrDefault(),
TotalQuantity = acc.TotalQuantity + t.Quantity.GetValueOrDefault(),
});

How can I Make a LINQ query expression dynamic

I'm trying to implement cascading controls using the following LINQ query expression.
The idea is that I have three option lists represented by the tables OptionA, OptionB and OptionC and a view called OptionIndex with one column each for OptionA_ID, OptionB_ID, OptionC_ID and that table has of all the combinations of tags from the option lists that are in use. Left outer joining the OptionIndex on the option list produces a boolean for the Disabled attributed in the option tag.
How do I make the on clause, which is .Where(...) in the following sample code, allow for any combination of the controls being used?
For example, lets say the user initially selects option value 123 in OptionA. The code to return the Values, Labels and Disabled booleans for OptionC would look like the following:
from t1 in OptionCs
from t2 in OptionIndexes.Where(x => t1.OptionC_ID == x.OptionC_ID && new List<int> { 123 }.Contains(x.OptionA_ID)).DefaultIfEmpty()
group new {t1, t2} by new { t1.OptionC_ID, t1.Label } into g
select new { g.Key.OptionC_ID, g.Key.Label, Disabled = g.Count(t => t.t2.OptionC_ID == null) > 0 }
Then lets say the user selects option values 456 and 789 in OptionB. The code to return the Values, Labels and Disabled booleans for OptionC change to:
from t1 in OptionCs
from t2 in OptionIndexes.Where(x => t1.OptionC_ID == x.OptionC_ID && new List<int> { 123 }.Contains(x.OptionA_ID) && new List<int> { 456, 789 }.Contains(x.OptionB_ID)).DefaultIfEmpty()
group new {t1, t2} by new { t1.OptionC_ID, t1.Label } into g
select new { g.Key.OptionC_ID, g.Key.Label, Disabled = g.Count(t => t.t2.OptionC_ID == null) > 0 }
To make the example code easier to understand I used new List<int>. In the actual project, however I would be passing the integers from the option list in as integer arrays from the controls themselves.
The trick is somehow making the query expression dynamic so that it can represent any combination of 0 to N multi-select controls being used or passing something that tells the join to accept any value for any given control such as
{x.OptionB_ID.Any}.Contains(x.OptionB_ID)
What is the best way to handle this?
Thanks!
Distilling your issue down to a simple example, consider this list of integers:
List<int> l = new List<int> { 1, 25, 3, 99, -23, 0, 15, 75 };
Say that you want to conditionally filter this list based on external criteria. Sometimes you want positive numbers, sometimes you want numbers smaller than 50, sometimes you want numbers divisible by 5, or any combination of these. Applying all filters with a static expression would look like this:
l.Where(n => n > 0).Where(n => n < 50).Where(n => n % 5 == 0);
To apply any or all of these dynamically, just build the LINQ query in pieces:
// These switches simulate your external conditions.
bool conditionA = true;
bool conditionB = false;
bool conditionC = true;
IEnumerable<int> myList = l;
if (conditionA) { myList = myList.Where(n => n > 0 ); }
if (conditionB) { myList = myList.Where(n => n < 50 ); }
if (conditionC) { myList = myList.Where(n => n % 5 == 0); }
With the switches set as in my example, the output is 25, 15, 75.
Side note: if you are not aware of it, use LINQPad to experiment with things like this. It is a fantastic tool for essentially executing code interactively, be it LINQ code or not. When I built the above sample, I inserted myList.Dump(); calls after each of the last 4 lines so I could see how each filter was applied. Here is the output:

Removing values from a returned linq query

HI there I am hoping for some help with a query I have.
I have this query
var group =
from r in CustomerItem
group r by r.StoreItemID into g
select new { StoreItemID = g.Key,
ItemCount = g.Count(),
ItemAmount = Customer.Sum(cr => cr.ItemAmount),
RedeemedAmount = Customer.Sum(x => x.RedeemedAmount)
};
I am returning my results to a list so I can bind it listbox.
I have a property called EntryType which is an int. There are 2 available numbers 1 or 2
Lets say I had 3 items that my query is working with
2 of them had the EntryType = 1 and the 3rd had EntryType2. The first records had a ItemAmount of 55.00 and the 3rd had a ItemAmount of 50.00
How can I group using something simlar to above but minus the ItemAmount of 50.00 from the grouped amount to return 60.00?
Any help would be great!!
It's not really clear what the question is - are you just trying to ignore all items with an entry type of 2? To put it another way, you only want to keep entries with an entry type of 1? If so, just add a where clause:
var group = from r in CustomerItem
where r.EntryType == 1
group r by r.StoreItemID into g
select new {
StoreItemID = g.Key, ItemCount = g.Count(),
ItemAmount = Customer.Sum(cr => cr.ItemAmount),
RedeemedAmount = Customer.Sum(x => x.RedeemedAmount)
};
Change ItemAmount = ... to:
ItemAmount =
g.Where(x => x.EntryType == 1).Sum(cr => cr.ItemAmount) -
g.Where(x => x.EntryType == 2).Sum(cr => cr.ItemAmount),
I changed Customer to g because this seems to be an error, but it's not clear to me from your question what you mean here, so maybe this change is not what you want.
A slightly more concise method is to use test the entry type in the sum and use the ternary operator to choose whether to add the positive or negative value:
ItemAmount = g.Sum(cr => cr.EntryType == 1 ? cr.ItemAmount : -cr.ItemAmount),
This gives the value of 60.00 as you required.

Resources