Im having a problem with Linq using Lambda Expressions. Im trying to do this select
SELECT L.IDLLAMADO FROM LLAMADOS L WHERE EXISTS (SELECT D.IDLLAMADO FROM DIAGNOSTICO D WHERE D.DESCRIPCION LIKE '%SOME VALUE%')
Some notes:
Im using IQueryable because Im appending joins and where depending on
the parameters of the method.
In the example I mapped only a property in my complex class because the rest are not useful in the example.
diagonostico is a string parameter.
My code is:
DbSet<Llamados> llamados = context.Set<Llamados>();
IQueryable<ComplexLlamadosAfil> query = llamados
.Select(e => new ComplexLlamadosAfil { IdLlamado = e.IdLlamado });
//If some conditions
query = query.Where(e => diagnosticos.Any(d => d.IdLlamado == e.IdLlamado &&
d.Descripcion.Contains(diagnostico)) == true);
There is not compilation error but when i run this i keep getting this error:
The object type
'System.Data.Objects.ObjectQuery`1[GAMP.MO.VW_DIAGNOSTICOS_LLAMADO]'
cannot be converted into
'System.Data.Entity.DbSet`1[GAMP.MO.VW_DIAGNOSTICOS_LLAMADO]'.
I also tried using .TakeWhile() instead of .Where() but no success.
Thanks for reading guys,
Pablo.
well, the message is rather clear, no ?
I would do
//Don't tell that it's a DbSet<Llamados>. You want an IQueryable<Llamados>
IQueryable<Llamados> llamados = context.Set<Llamados>().AsQueryable();
IQueryable<ComplexLlamadosAfil> query = llamados
.Select(e => new ComplexLlamadosAfil { IdLlamado = e.IdLlamado });
//If some conditions
query = query.Where(e => diagnosticos.Any(d => d.IdLlamado == e.IdLlamado &&
d.Descripcion.Contains(diagnostico)) == true);
or just use var
var llamados = context.Set<Llamados>().AsQueryable();
var query = llamados
.Select(e => new ComplexLlamadosAfil { IdLlamado = e.IdLlamado });
//If some conditions
query = query.Where(e => diagnosticos.Any(d => d.IdLlamado == e.IdLlamado &&
d.Descripcion.Contains(diagnostico)) == true);
Related
I need help with Linq Contains method. Here's the code below.
This code does work but outputs an empty sets.
var query = _context.RegistrationCodes.Select(x => x);
if (request.OperatorId != null && request.OperatorId != Guid.Empty)
{
var checkOperator = _context.Operators.Include(a => a.OperatorLevel).Include(a => a.City).Include("City.StateRegion.Country").FirstOrDefault(a => a.Id == request.OperatorId);
List<String> Cities = new List<String>();
if (checkOperator.OperatorLevel.Name == "City")
{
Cities = await _context.Cities
.Where(a => (checkOperator.CityId) == (a.Id))
.Select(a => a.Code)
.ToListAsync();
}
else if (checkOperator.OperatorLevel.Name == "Regional")
{
Cities = await _context.Cities
.Where(a => checkOperator.City.StateRegionId == a.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
}
else if (checkOperator.OperatorLevel.Name == "National")
{
List<Guid> StateRegion = await _context.StateRegions
.Where(a => checkOperator.City.StateRegion.CountryId == a.CountryId)
.Select(a => a.Id)
.ToListAsync();
Cities = await _context.Cities
.Where(a => StateRegion.Contains(a.StateRegionId))
.Select(a => a.Code)
.ToListAsync();
}
var nullableStrings = Cities.Cast<String?>().ToList();
query = query.Where(a => nullableStrings.Contains(a.Code));
}
I need to compare nullableStrings to a.Code which is something like this, but does not work.
query = query.Where(a => a.Code.Contains(nullableStrings));
Error : Argument 1: cannot convert from 'System.Collections.Generic.List' to 'char'
I need a method that would replace
query = query.Where(a => nullableStrings.Contains(a.Code));
A help would be appreciated. Thanks.
Looking at the code, my guess is the requirement is to get a list of operators depending on the current (check) operator's level. I suspect the issue you are encountering is that some cities may not have a code. You then want to apply all found codes to another query that you are building up.
My guess is that the crux of the problem is that some cities might not have a code, hence the concern for null-able strings, while others might have multiple codes hacked into a single-code intended field. The solution there would typically be to remove any null values
Firstly, this line:
var checkOperator = _context.Operators.Include(a => a.OperatorLevel).Include(a => a.City).Include("City.StateRegion.Country").FirstOrDefault(a => a.Id == request.OperatorId);
can be simplified to:
var checkOperator = _context.Operators
.Select(a => new
{
Level = a.OperatorLevel.Name,
CityId = a.City.Id,
CityCode = a.City.Code,
StateRegionId = a.City.StateRegion.Id,
CountryId = a.City.StateRegion.Country.Id
}).FirstOrDefault(a => a.Id == request.OperatorId);
This builds a faster query, rather than fetching an entire operator object graph, just select the fields from the object graph that we need.
Now to handle the operator level. Here I don't recommend trying to force every scenario into a single pattern. The goal is just to apply a filter to the built query, so have the scenarios do just that:
select (checkOperator.Level)
{
case "City":
query = query.Where(a => a.Code == checkOperator.CityCode);
break;
case "Regional":
var cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Id == checkOperator.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Country":
var cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Country.Id == checkOperator.CountryId)
.Select(a => a.Code)
.ToListAsync();
query = query.Where(a => cityCodes.Contains(a.Code));
break;
}
Now based on the comments it sounds like your data with cities and codes is breaking proper normalization where Code was intended as a 1-to-1 but later hacked to handle one city having multiple codes, so multiple values were concatenated with hyphens. (I.e. ABC-DEF) If this represents 2 Codes for the city then you will need to handle this..
private List<string> splitCityCodes(List<string> cityCodes)
{
if (cityCodes == null) throw NullReferenceException(nameof(cityCodes));
if (!cityCodes.Any()) throw new ArgumentException("At least one city code is expected.");
var multiCodes = cityCodes.Where(x => x.Contains("-")).ToList();
if (!multiCodes.Any())
return cityCodes;
var results = new List<string>(cityCodes);
results.RemoveRange(multiCodes);
foreach(var multiCode in multiCodes)
{
var codes = multiCode.Split("-");
results.AddRange(codes);
}
return results.Distinct();
}
That can probably be optimized, but the gist is to take the city codes, look for hyphenated values and split them up, then return a distinct list to remove any duplicates.
List<string> cityCodes = new List<string>();
select (checkOperator.Level)
{
case "City":
cityCodes = splitCityCodes(new []{checkOperator.CityCode}.ToList());
if(cityCodes.Count == 1)
query = query.Where(a => a.Code == cityCodes[0]);
else
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Regional":
cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Id == checkOperator.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
cityCodes = splitCityCodes(cityCodes);
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Country":
cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Country.Id == checkOperator.CountryId)
.Select(a => a.Code)
.ToListAsync();
cityCodes = splitCityCodes(cityCodes);
query = query.Where(a => cityCodes.Contains(a.Code));
break;
}
... and I suspect that would about do it for handling the possibility of a city code containing multiple values.
If your search argument is in the form "ABC-DEF" and you want that to match "ABC" OR "DEF" then it can be done, but it is not clear from your data setup how that scenario comes about.
Lets assume these codes are airport codes, and that a city that has multiple airports has the City.Code as a hyphenated list of the Airport codes, then if the checkOperator is in Australia, and their OperatorLevel is "National" then this might build the following nullableStrings:
var Cities = new List<string> {
"PER",
"ADE",
"DRW",
"MEL-AVV",
"SYD",
"BNE",
"OOL",
"HBA"
};
If then your query is a listing of AirPorts and you want to search the airports by these codes, specifically to match both "MEL" and "AVV" then you can use syntax like this
var nullableStrings = Cities.Cast<String?>().ToList();
query = query.Where(ap => nullableStrings.Any(n => n.Contains(ap.Code)));
But if you intend this to be translated to SQL via LINQ to Entities (so be executed server-side) then we can make this query more efficient buy normalizing the search args so we can do an exact match lookup:
var nullableStrings = Cities.Where(x => !String.IsNullOrWhiteSpace (x))
.SelectMany(x => x.Split('-'))
.Cast<String?>()
.ToList();
query = query.Where(ap => nullableStrings.Contains(ap.Code));
As this routine is called as part of a larger set and your checkOperator goes out of scope, you should try to reduce the fields that you retrieve from the database to the specific set that this query needs through a projection
Using .Select() to project out specific fields can help improve the overall efficiency of the database, not just each individual query. If the additional fields are minimal or natural surrogate keys, and your projections are common to other query scenarios then they can make good candidates for specific index optimizations.
Instead of loading SELECT * from all these table in this include list:
var checkOperator = _context.Operators.Include(a => a.OperatorLevel)
.Include(a => a.City.StateRegion.Country)
.FirstOrDefault(a => a.Id == request.OperatorId);
So instead of all the fields from OperatorLevel, City, StateRegion, Country we can load just the fields that our logic needs:
var checkOperator = _context.Operators.Where(o => o.Id == request.OperatorId)
.Select(o => new {
OperatorLevelName = o.OperatorLevel.Name,
o.CityId,
o.City.StateRegionId,
o.City.StateRegion.CountryId
})
.FirstOrDefault();
So many of the EF has poor performance opinions out there stem from a lot of poorly defined examples that proliferate the web. Eagerly loading is the same as executing SELECT * FROM ... for simple tables it's only a bandwidth and memory waste, but for complex tables that have computed columns or custom expressions there can be significant server CPU costs.
It cannot be overstated the improvements that you can experience if you use projections to expose only the specific sub-set of the data that you need, especially if you will not be attempting to modify the results of the query.
Be a good corporate citizen, only take what you need!
So lets put this back into your logic:
if (request.OperatorId != null && request.OperatorId != Guid.Empty)
{
var checkOperator = _context.Operators.Where(o => o.Id == request.OperatorId)
.Select(o => new {
OperatorLevelName = o.OperatorLevel.Name,
o.CityId,
o.City.StateRegionId,
o.City.StateRegion.CountryId
})
.FirstOrDefault();
IQueryable<City> cityQuery = null;
if (checkOperator.OperatorLevelName == "City")
cityQuery = _context.Cities
.Where(a => checkOperator.CityId == a.Id);
else if (checkOperator.OperatorLevelName == "Regional")
cityQuery = _context. Cities
.Where(a => checkOperator.StateRegionId == a.StateRegionId);
else if (checkOperator.OperatorLevelName == "National")
cityQuery = _context. Cities
.Where(c => c.StateRegion.CountryId == checkOperator.CountryId);
// TODO: is there any default filter when operator level is something else?
if (cityQuery != null)
{
var nullableStrings = cityQuery.Select(a => a.Code)
.ToList()
.Where(x => !String.IsNullOrWhiteSpace(x))
.SelectMany(x => x.Split('-'))
.Cast<String?>()
.ToList();
query = query.Where(ap => nullableStrings.Contains(ap.Code));
}
}
If you don't want or need to normalize the strings, then you can defer this whole expression without realizing the city query at all:
// No nullable string, but we can still remove missing Codes
cityQuery = cityQuery.Where(c => c.Code != null);
query = query.Where(ap => cityQuery.Any(c => c.Code.Contains(ap.Code)));
I have to fix a query which was already written in the LINQ Lambda, I found the fix in a Simple SQL Query but now I have some trouble in converting it to LINQ Query,
Here is my SQL Query
select * from RequestItem_SubRequestItem x
where x.RequestItem_key = 1 and x.SubRequestItem_key in (
select o.SubRequestItem_key
from SubRequestItem_Entitlement o
inner join SubRequestItem sr on sr.SubRequestItem_key = o.SubRequestItem_key
where o.Entitlement_key = 2 and sr.Action = 'Add' )
And below is my LINQ C# code where I am trying to insert my fixes which include inner join.
z.Entitlements = ARMContext.Context.SubRequestItem_Entitlement
.Where(o => o.Entitlement_key == z.AccessKey && !o.Role_key.HasValue && o.Entitlement.EntitlementConfiguration.UserVisible == true
&& (ARMContext.Context.RequestItem_SubRequestItem
.Where(x => x.RequestItem_key == requestItemKey)
.Select(y => y.SubRequestItem_key)
.Contains(o.SubRequestItem_key)))
.Join(ARMContext.Context.SubRequestItems, subrq => subrq.SubRequestItem_key, temp => requestItemKey, (subrq, temp) => subrq == temp)
Where as previously the C# LINQ code looked like this
z.Entitlements = ARMContext.Context.SubRequestItem_Entitlement
.Where(o => o.Entitlement_key == z.AccessKey && !o.Role_key.HasValue && o.Entitlement.EntitlementConfiguration.UserVisible == true
&& (ARMContext.Context.RequestItem_SubRequestItem
.Where(x => x.RequestItem_key == requestItemKey)
.Select(y => y.SubRequestItem_key)
.Contains(o.SubRequestItem_key)))
When I try to insert the JOIN in the LINQ as per my conditions then I get to see this error.
What is my mistake? Can anybody tell me a correct way to do it?
I think this should Suffice your need, although you might have to make changes to the other code which are dependent on your SubRequestItem_Entitlement table with {user, add}
please have a look at that. As I am sure you will have to make those changes.
.Join(ARMContext.Context.SubRequestItems, user => user.SubRequestItem_key, subreqItems => subreqItems.SubRequestItem_key, (user, subreqItems) => new { user, subreqItems })
.Where(Action => Action.subreqItems.Action == z.ApprovalAction)
you can use this query. I exactly matched the SQL query
var query = ARMContext.Context.RequestItem_SubRequestItem
.Where(a => a.RequestItem_key == 1 && a.RequestItem_key == (ARMContext.Context.SubRequestItem_Entitlement
.Join(ARMContext.Context.SubRequestItems,
right => right.SubRequestItem_key,
left => left.SubRequestItem_key,
(right, left) => new
{
right = right,
left = left
})
.Where(x => x.right.Entitlement_key == 2 && x.left.Action == "Add" && x.right.SubRequestItem_key == a.RequestItem_key).Select(y => y.right.SubRequestItem_key)).FirstOrDefault());
I am having difficulty converting this expression to SQL expression. I tried various applications but I failed. I tried to convert it myself but the result is different.
db.Trans_SAPStat.Where(s => s.EmployeeID == EmployeeID && s.PaymentID != PaymentID && !s.Status.Equals("cancelled or", StringComparison.OrdinalIgnoreCase))
.Join(_db.Trans_PaymentDetail,
stat => stat.PaymentID,
paydet => paydet.PaymentID,
(stat, paydet) => new
{
InvoiceNo = paydet.Remarks,
paydet.Amount,
stat.Status,
paydet.PaymentCode
})
.Where(s => s.PaymentCode.ToLower() == "c")
.GroupBy(g => g.InvoiceNo)
.Select(lg =>
new
{
InvoiceNo = lg.Key,
TotalAmount = lg.Sum(w => w.Amount)
}).ToList();
If you have this code inside working application then try to use SQL profiler. Another option - linqpad
Try this:
SELECT
p.Remarks AS InvoiceNo,SUM(p.Amount) AS TotalAmount
FROM Trans_SAPStat AS s
INNER JOIN Trans_PaymentDetail AS p ON s.PaymentID = p.PaymentID
WHERE s.EmployeeID = #EmployeeID
AND s.PaymentID <> #PaymentID
AND LOWER(s.Status) <> 'cancelled or'
AND LOWER(s.PaymentCode) = 'c'
GROUP BY p.Remarks
You can try to set the query to a variable without materialization (.ToList()), then just use query.ToString(). This will return the actual SQL query.
var query = db.Trans_SAPStat.Where(....); // don't call .ToList()
var sqlQuery = query.ToString();
var result = query.ToList(); // materialization
NOTE: This is the default behaviour for entity framework / linq to entities IQueryable<T> interface.
I need to run this query in a oracle 11.2 database, but the generated query contains a "OUTER APPLY". How can I solve it ?
var query = from r in Ctx.Reg
let status_1 = (r.Hist.OrderByDescending(o => o.Id).FirstOrDefault(h => h.RegId == r.Id).Status == 1)
let status_2 = (r.Hist.OrderByDescending(o => o.Id).Skip(1).FirstOrDefault(h => h.RegId == r.Id).Status == 2)
select new
{
r.Id,
...
status_1,
status_2
};
OUTER APPLY is generated by your let statement expressions. You can avoid it by turning them into EXISTS translatable expressions using the equivalent Any based LINQ constructs:
var query = from r in Ctx.Reg
let status_1 = r.Hist.OrderByDescending(h => h.Id).Take(1).Any(h => h.Status == 1)
let status_2 = r.Hist.OrderByDescending(h => h.Id).Skip(1).Take(1).Any(h => h.Status == 2)
select new
{
r.Id,
...
status_1,
status_2
};
Note that h.RegId == r.Id is not needed because that is implied by r.Hist navigation property.
I am working on a project to retrieve some data from a website. To complete the job, I decided to utilize the HtmlAgilityPack.
Everything is working fine except an issue I am facing related to lambda expressions. The error I am receiving is
Cannot implicitly convert type
'System.Collections.Generic.IEnumerable'
to 'bool'
var page = RetrievePage(url); //retrieve page
var document = GetDocument(page); //get the document
var optionNodes = document.Result.DocumentNode.SelectNodes("???"); //Select nodes based on selector
//remove empty lines
var filteredNodes = optionNodes[0].ChildNodes.Where(n => n.InnerText.Trim() != string.Empty);
using (var nodes = filteredNodes.GetEnumerator())
{
while (nodes.MoveNext())
{
//error
var children = nodes.Current.ChildNodes.Where(c => c.Attributes.Where(t => t.Value == "???"));
}
}
Appreciate your help.
The inner where will return rows. The outer where can only be an expression which has a boolean. So you have to change this:
var children = nodes.Current.ChildNodes
.Where(c => c.Attributes.Where(t => t.Value == "???"));
To this:
var children = nodes.Current.ChildNodes
.Where(c => c.Attributes.Any(t => t.Value == "???"));
Or this:
var children = nodes.Current.ChildNodes
.Where(c => c.Attributes.All(t => t.Value == "???"));
Or this:
var children = nodes.Current.ChildNodes
.Where(c => c.Attributes.Where(t => t.Value == "???").Count()>0);
Reference:
Enumerable.Any Method (IEnumerable, Func)
Enumerable.All Method