Lambda Expression LINQ Equivalent to SQL Exists Query on Same Table/Variable - c#

I am attempting to replicate the following SQL query in LINQ using the lambda expression format (to keep it consistent with the code developed so far):
SELECT *
FROM Product p
WHERE p.DateObsolete IS NULL
OR p.DateObsolete > GETDATE()
OR EXISTS (
SELECT NULL
FROM dbo.Product p1
WHERE p1.Ref01 = p.Ref01
AND p1.Ref02 = p.Ref02
AND p1.Ref03 = p.Ref03
AND p1.Version = p.Version + 1
AND p1.DateApproved IS NULL
)
Having looked at other questions (Linq subquery same table using lambda was the closest I could find but but didn't show how to "or" conditions) on SO and elsewhere I thought the following would work but it just causes a stack overflow (seriously) exception and a message about a pdb file not being loaded, which I think is a bit of a red herring.
products = products
.Where(p => !p.DateObsolete.HasValue
|| p.DateObsolete > DateTime.Now
|| products.Any(p1 => p1.Ref01 == p.Ref01
&& p1.Ref02 == p.Ref02
&& p1.Ref03 == p.Ref03
&& p1.Version == p.Version + 1
&& p1.DateApproved == null));
products is an IQueryable variable.
Product is defined in this context as DbSet of:
public class Product
{
public int ProductID { get; set; }
[MaxLength(200)]
public string Name { get; set; }
[MaxLength(3)]
public string Ref01 { get; set; }
[MaxLength(3)]
public string Ref02 { get; set; }
public int Ref03 { get; set; }
public int Version { get; set; }
public DateTime? DateReceivedByGraphics { get; set; }
public DateTime? DateApproved { get; set; }
public DateTime? DateObsolete { get; set; }
public bool Deleted { get; set; }
public bool Discontinued { get; set; }
}
As you may gather I'm new to LINQ (and to posting questions on SO) so any help would be gratefully received.

You might be able to accomplish this with a Join.
DateTime date = DateTime.Today; // or .Now
var products = context.Products.Join(context.Products,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { Product = p1, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.Product.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.Product)
.ToList();
This joins Product to itself on Ref 1-3, but then selects the "left" side project, along with it's version, the "right" side's version and date approved. The Where condition isolates cases where the "right" version is 1 greater than the left and has no date approved. The result will be the "left" products that have counterparts that match those criteria.
Update:
If you have already filtered the products down to a known set of applicable products, then this will work against Objects. For example:
// given products is an IQueryable representing the filtered products...
DateTime date = DateTime.Today; // or .Now
var productList = products.ToList(); // Materialize the EF Queryable into list of entities.
productList = productList.Join(productList,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { Product = p1, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.Product.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.Product)
.ToList();
If your goal is to try and keep this as an IQueryable scoped to EF then I'd suspect that if it's possible, it might not be worth the complexity/time. Worst-case if you did want to preserve the IQueryable, use the above to select Product IDs into a list, then apply that list as a filter against the IQueryable.
var productList = products.ToList(); // Materialize the EF Queryable into list of entities.
// Fetch a list of applicable product IDs.
var productIds = productList.Join(productList,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { ProductId = p1.ProductId, DateObsolete = p1.DateObsolete, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.ProductId)
.ToList();
// Filter the original IQueryable.
products = products.Where(x => productIds.Contains(x.ProductId));

It was as Aleks Andreev and Ivan Stoev suggested that assigning the expression to a new variable sorted out the problem. I'm not sure why this didn't work the first time but my guess is that, after completing the query I tried to re-assign the result back to the original variable - in order not to have to change the variable name in all the code that followed my change.

Related

Using LINQ in my Business Logic, but getting an error

The idea is that a Reference Number or Vendor can be passed to the logic and it spits back the query results for the search. I know i'm missing something.. Just can't see it..
The SQL query idea was as follows..
"SELECT ReferenceNumber, VendorName, RequisitionStatus, RequestedOn, sum(RequisitionQTY*RequisitionPrice) as Total FROM partrequisition where ReferenceNumber = 105543 Group By 'ReferenceNumber' "
Can anyone help?
I'm getting the following error:
CS0029 Cannot implicitly convert type
System.Collections.Generic.List<decimal?> to
System.Data.Entity.DbSet<PartsManagement.Models.partrequisition>
(contoller)
public ActionResult Index(RequisitionSearch searchModel)
{
var PartRequisitionInfoLogic = new PartRequisitionInfoLogic(); //This is where any business Logic goes
var Model = PartRequisitionInfoLogic.Getpartrequisitions(searchModel); //This is where it figures out what Search Query is
var RequisisionResults = Model.ToList();
}
(Business Logic)
private wsdpartsmanagementEntities9 db = new wsdpartsmanagementEntities9();
public IQueryable<partrequisition> Getpartrequisitions(RequisitionSearch searchModel)
{
var result = db.partrequisitions.AsQueryable();
if (searchModel != null)
{
if (searchModel.ReferenceNumber != 0 && searchModel.VendorID == 0)
result = result.Where(c => c.ReferenceNumber == searchModel.ReferenceNumber).GroupBy(c => c.ReferenceNumber)
.Select(g => g.Sum(item => item.RequisitionQTY * item.RequisitionPrice)) // For each group, calculate the sum
.ToList();
if (searchModel.ReferenceNumber == 0 && searchModel.VendorID != 0)
result = result.Where(c => c.VendorName == searchModel.VendorID).GroupBy(c => c.ReferenceNumber)
.Select(g => g.Sum(item => item.RequisitionQTY * item.RequisitionPrice))// For each group, calculate the sum
.ToList();
}
return result;
}
(ViewModel(s))
public class RequisitionSearch
{
public int? VendorID { get; set; }
public int? ReferenceNumber { get; set; }
public List<RequisitionResults> SearchResults { get; set; }
public RequisitionSearch()
{
this.SearchResults = new List<RequisitionResults>();
}
}
}
public class RequisitionResults
{
public int ReferenceID { get; set; }
[Display(Name = "Reference Number")]
public int ReferenceNumber { get; set; }
[Display(Name = "Vendor Name")]
public string VendorName { get; set; }
[Display(Name = "Requisition Status")]
public string RequisitionStatus { get; set; }
[Display(Name = "Requisition On")]
public DateTime RequestedOn { get; set; }
[DisplayFormat(DataFormatString = "{0:C}")]
[Display(Name = "Requisition Total")]
public decimal RequisitionTotal { get; set; }
}
Update.. The solution was to scrap the Business logic and replace it with (below) in the ActionResult. Works perfectly.
var result = db.partrequisitions.AsQueryable();
if (searchModel.ReferenceNumber != null || searchModel.VendorID != null)
{
result = result.Where(partrequisition => partrequisition.ReferenceNumber == searchModel.ReferenceNumber || partrequisition.VendorName == searchModel.VendorID);
}
else
{
result = result.Distinct();
}
var b = result.GroupBy(x => x.ReferenceNumber);
var c = b.Select(group => group.Sum(partRequistion => partRequistion.RequisitionQTY * partRequistion.RequisitionPrice));
And to access the query information, a couple of foreach loops..
Have a look at the following line carefully:
result = result.Where(c => c.VendorName == searchModel.VendorID)
.GroupBy(c => c.ReferenceNumber)
.Select(g => g.Sum(item => item.RequisitionQTY * item.RequisitionPrice))// For each group, calculate the sum
.ToList();
You are trying to return a List Of Decimal? values using the Sum method while your method's return type is IQueryable<partrequisition>. So what you need is either changing your method's return type to a List of Decimals? or change your query in order to fulfill what your method actually needs by returning an IQueryable of partrequisition.
If you had split your big query into smaller ones, and used the keyword var a lot less, then you would have seen your problem at compile time:
Alas you forgot to describe your class PartRequisition. From your code it looks similar to:
class PartRequisition
{
public int ReferenceId {get; set;}
public int ReferenceNumber {get; set;}
public decimal? RequisitionQty {get; set;}
public decimal? RequisitionPrice {get; set;}
// one of them might be not a decimal? but a decimal.
}
Your code:
IQueryable<PartRequisition> result = db.partrequisitions.AsQueryable();
result = result
.Where(partRequistion => partRequisition.ReferenceID == searchModel.ReferenceNumber)
.GroupBy(partRequistion => partRequistion.ReferenceNumber)
.Select(group => group.Sum(partRequistion =>
partRequistion.RequisitionQTY * partRequistion.RequisitionPrice))
.ToList();
Let's split this into smaller parts:
IQueryable<PartRequisition> result = ...
IQueryable<PartRequisition> a = result.Where(partRequistion => ... == ...);
IQueryable<IGrouping<int, PartRequisition>> b = a.GroupBy(
partRequistion => partRequistion.ReferenceNumber);
So b is a queryable sequence of groups. Every group has an integer property Key. Each group is also a sequence of PartRequistions, namely all PartRequisitions that have a value of ReferenceNumber equal to the Key.
In the Select, you take each group of your sequence of groups. From every element in this group (which is a PartRequisition) you multiply two decimal? properties, which gives us a decimal? result. After that you sum these results, using Sum<decimal?>
IQueryable<decimal?> c = b.Select(group => group.Sum(partRequistion =>
partRequistion.RequisitionQTY * partRequistion.RequisitionPrice)
Or in even smaller steps:
c is IQueryable>`
Every group is IGrouping<int, PartRequisition>
every partRequisition is a PartRequisition
every RequisitionQTY and RequisitionPrice are decimial?
the result of the multiplication is decimal?
the result of the Sum is decimal?
d is IQueryable
Finally:
List<decimal?> d = c.ToList();
result = d; // remember, result is an IQueryable<PartRequisition>
It is clear to see that you can't assign a List<decimal?> to an IQueryable<PartRequisition>, which was exactly what the error said:
CS0029 Cannot implicitly convert type List to DbSet
Back to your problem
You descirbed a class RequistionResult that you didn't use in method GetRequisitions.
Can it be that you don't want to return an IQueryable<PartRequistions>
but an IQueryable<RequisitionResult>,
where property RequisitionTotal is your calculated Sum?
public IQueryable<RequisitionResult> GetRequisitions(...)
{
IQueryable<PartRequisition> partRequisitions = db.partrequisitions.AsQueryable();
if (searchModel != null)
{
if (searchModel.ReferenceNumber != 0 && searchModel.VendorID == 0)
{
partRequisitions = partRequisitions.Where(...);
}
else if (searchModel.ReferenceNumber == 0 && searchModel.VendorID != 0)
{
partRequisitions = partRequisitions.Where(...);
}
// else use all PartRequisitions
}
else
{
// TODO: decide what to return if searchModel is null, for example
partRequisitions = IQueryable.Empty<PartRequisition>();
// or use all PartRequisitions
}
// from the remaining set of PartRequisitions, convert to RequisitionResults:
IQueryable<RequisitionResult> requisitionResults = partRequisitions
.GroupBy(partRequisition => partRequisition.ReferenceNumber,
// parameter ResultSelector: take each ReferenceNumber and all PartRequisitions
// with this ReferenceNumber to make one new RequisitionResult
(referenceNumber, partRequisitionsWithThisReferenceNumber) => new RequisitionResult
{
RequisitionTotal = partRequisitionsWithThisReferenceNumber
.Select(partRequisition => partRequisition.RequisitionQty * partRequisition.RequisitionPrice)
.Sum(),
// fill other properties
...
});
return requisitionResults;
}

Return non-aggregates along with aggregates in Linq select

I am trying to convert the basic SQL query into Linq.
The aggregates are returning fine, but the non-aggregates are not.
SeriesId, Amount, and NumberOfTrades are perfect.
Code and Isin aren't causing errors, but they're returning the type rather than the data.
The query I'm trying to convert:
SELECT
trans_series.id,
trans_series.number,
trans_series.isin,
SUM( trans_trades.amount ),
COUNT( trans.series_id )
FROM
trans_trades
INNER JOIN trans_series ON trans_series.id = trans_trades.series_id
WHERE
trans_trades.series_id IN (
17,
18)
AND trans_trades.first_party_id IS NULL
AND trans_trades.status <> 'closed'
AND trans_trades.status <> 'cancelled'
GROUP BY trans_trades.series_id
The method:
public List<TotalByIsinViewModel> GetIssuerSeries()
{
_context = new MySQLDatabaseContext();
var result = (from ts in _context.TradesSeries
join tts in _context.TradesTrades
on ts.Id equals tts.SeriesId
where myInClause.Contains(tts.SeriesId)
group new { ts, tts } by new { tts.SeriesId } into g
select new TotalByIsinViewModel
{
SeriesId = g.Key.SeriesId,
Code = g.Select(i => i.tts.Number).Distinct().ToString(),
Isin = g.Select(i => i.ts.Isin).Distinct().ToString(),
Amount = (decimal?)g.Sum(pt => pt.tts.Amount),
NumberOfTrades = g.Count()
}).ToList();
return result;
}
The view model:
public class TotalByIsinViewModel
{
public int SeriesId { get; set; }
public string Code { get; set; }
public string Isin { get; set; }
public decimal? Amount { get; set; }
public int NumberOfTrades { get; set; }
}
I'm expecting the actual distinct varchar values from the "number" and "isin" columns, but I'm getting type data returned in my Razor page's cs Onget.
What you need is:
Code = g.First().tts.Number,
Isin = g.First()ts.Isin,
And the reason is in g.Select(i => i.tts.Number) you select a list of values.
Maybe you have only one value in list but C# still sees a list.
And ToString method for arrays is inherited from Object class. So it prints type name.
Fixed this problem by changing the "Isin" and "Code" to this:
Code = g.Select(i => i.ts.Number).Distinct().First(),
Isin = g.Select(i => i.ts.Isin).Distinct().First(),

Query with Where before GroupBy

I have an entity like this:
public class Event
{
public string Code;
public DateTimeOffset DateTime;
}
I want to filter by Code and then group by DateTime.Date. I tried this:
var results = session
.Query<Event>()
.Where(e => e.Code == "123")
.GroupBy(e => e.DateTime.Date)
.ToList();
But I get the following error:
Raven.Client.Exceptions.InvalidQueryException: Field 'Code' isn't neither an aggregation operation nor part of the group by key
Query: from Events group by DateTime.Date where Code = $p0
Parameters: {"p0":"123"}
It can be seen from the resulting query that the where clause is being added after the group by clause, which explains the error.
So how do I perform this query in RavenDB?
EDIT:
The code "123" that I used was just an example. I need it to be a variable that is passed to the query, like this:
var results = session
.Query<Event>()
.Where(e => e.Code == code)
.GroupBy(e => e.DateTime.Date)
.ToList();
To start with, learn about the dynamic aggregation query syntax in:
https://demo.ravendb.net/demos/auto-indexes/auto-map-reduce-index
But, in your case you need to define a Static Map-Reduce Index to calculate this for you:(Sum up the number of (filtered) documents per unique Date)
i.e.
public class Result
{
public string Date { get; set; }
public int NumberOfDocs { get; set; }
}
Map = events => from event in events
where event.Code == "123"
select new Result
{
Date = event.DateTime.Date
NumberOfDocs = 1
}
Reduce = results => from result in results
group result by result.Date into g
select new Result
{
Date = g.Key,
Count = g.Sum(x => x.NumberOfDocs )
}
==> Learn about Static Map-Reduce Index in:
https://demo.ravendb.net/demos/static-indexes/map-reduce-index
Follow the detailed Walkthrough..
----------
Update:
You can use the following map-reduce index that aggregates the number of documents per Code & Date 'couple', and then you can query with 'Code'
public class Result
{
public string Code { get; set; }
public string Date { get; set; }
public int NumberOfDocs { get; set; }
}
Map = events => from event in events
select new Result
{
Code = event.Code
Date = event.DateTime.Date
NumberOfDocs = 1
}
Reduce = results => from result in results
group result by new
{
result.Code,
result.Date
}
into g
select new Result
{
Code = g.Key.Code
Date = g.Key.DateTime.Date,
NumberOfDocs = g.Sum(x => x.NumberOfDocs )
}
and then query
List<Result> queryResults = session.Query< Result, <Index_Name> >()
.Where(x => x.Code == "some-code-number")
.ToList();
and then you can also do in your code
queryResults.GroupBy(x => x.Date)

LINQ how to Filter the data based on the value of the properties

i have a problem with filtering data in LINQ , here is my Model :
public class CoursePlan
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Semester { get; set; }
public string ModuleCode { get; set; }
public string ModuleName { get; set; }
public string Credits { get; set; }
public string OrderNumber { get; set; }
public string ModuleStatus { get; set; }
}
and here is my data Json
the problem here some modules having same OrderNumber which mean they are optional , student must study one of them and if student already study one of them , i should ignore other modules in same order number.
in other way to describe the question
i want to return a list of CoursePlan and on this list if there is two items having same OrderNumber check the ModuleStatus for each one of them and if any one is Completed remove other modules on that order otherwise return them all .
here is my code
var coursePlanList = await _sqLiteAsyncConnection.Table<CoursePlan>().ToListAsync();
var groupedData = coursePlanList.OrderBy(e => e.Semester)
.GroupBy(e => e.OrderNumber)
.Select(e => new ObservableGroupCollection<string, CoursePlan>(e))
.ToList();
for now im solving this by this algorithm and not sure if it's the best
var coursePlanList = await _sqLiteAsyncConnection.Table<CoursePlan>().ToListAsync();
List<CoursePlan> finalList = new List<CoursePlan>();
var counter = 0;
foreach (var itemPlan in coursePlanList)
{
if (counter > 0 && counter < coursePlanList.Count)
if (itemPlan.OrderNumber == coursePlanList[counter - 1].OrderNumber)
{
if (itemPlan.ModuleStatus == "Completed")
{
finalList.RemoveAll(a => a.OrderNumber == itemPlan.OrderNumber);
finalList.Add(itemPlan);
}
Debug.WriteLine(itemPlan.ModuleName + "With -->" + coursePlanList[counter - 1].ModuleName);
}
else
finalList.Add(itemPlan);
counter++;
}
var groupedData = finalList.OrderBy(e => e.ModuleStatus)
.ThenBy(e => e.Semester)
.GroupBy(e => e.Semester)
.Select(e => e)
.ToList();
CoursePlanViewList.BindingContext = new ObservableCollection<IGrouping<string, CoursePlan>>(groupedData);
Any advise or guidance would be greatly appreciated
Let me rephrase your requirement: you want to show all plans per OrderNumber that meet the condition: none of the plans in their group should be "Completed" or the plans themselves should be "Completed". All this grouped by Semester:
var plansQuery =
from p in _sqLiteAsyncConnection.Table<CoursePlan>()
group p by p.Semester into sem
select new
{
PlansInSemester =
from p in sem
group p by p.OrderNumber into gp
select new
{
PlansInOrderNumber =
gp.Where(p => !gp.Any(p1 => p1.ModuleStatus == "Completed")
|| p.ModuleStatus == "Completed")
}
};
This gives you an IQueryable that produces the course plans you want to select, but grouped in two levels, so the final result is obtained by flattening the query twice:
var coursePlanList = await plansQuery
.SelectMany(x => x.PlansInSemester
.SelectMany(y => y.PlansInOrderNumber)).ToListAsync()

Linq "join" with a IList<T> getting "Error Unable to create a constant value.."

I have a Save Method that saves with a Linq query a manually re-orderd list (in a web form) that is passed as the parameter to my method, and I try to update the Order Property of the IEnumerable<VM_CategoryLabel> I retrieve from the database (EF) with the corresponding value in the list (maybe would that be clearer with my code below):
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
//Order = x.Sequence_Cat,
Order = (int)listTemplate.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
I used the "test" var to see if my "sub-query" gets the correct value, and it does, but when I use my Linq expression inside the Select (the commented Order line), I get the following error:
Unable to create a constant value of type 'Namespace.Models.VM_CategoryLabelExtra. "Only primitive types and enumeration types are supported in this context.
Here are my classes:
public class VM_CategoryLabel
{
public int Id { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
public class VM_CategoryLabelExtra
{
public int Id { get; set; }
public int IdCat { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
So I suppose that I should not query the list inside my query ? So how do I "match" the 2 lists of values ?
I also tried the following (after having replace in the Linq query: Order = x.Sequence_Cat)that is not working neither because the iteration variable is
read-only:
foreach (var item in requete)
{
item.Order = listTemplate.Where(x => x.Id == item.Id).Select(x => x.Order).FirstOrDefault();
}
try
{
context.SaveChanges();
I suggest using this.
It is the let clause.
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
let list = listTemplate
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
Order = list.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
edit: instead offrom you can just do let list = listTemplate
Should work now :)
example for let:
// The let keyword in query expressions comes in useful with subqueries: it lets
// you re-use the subquery in the projection:
from c in Customers
let highValuePurchases = c.Purchases.Where (p => p.Price > 1000)
where highValuePurchases.Any()
select new
{
c.Name,
highValuePurchases
}
If you do not know how Let working than please download LinqPad and see an example

Categories