How To Use Yield Inside A Linq Query - c#

I have the following method:
public string GetDepartmentTitle(string DepartmentAbbreviation) {
List<TaxonomyItem> Divisions = TaxonomyFromCMS.GetAllItems(DomainDataConstants.DivisionAndDepartment.TAXONOMY_ID);
List<TaxonomyItem> Departments = new List<TaxonomyItem>();
Divisions.ForEach(delegate(TaxonomyItem Division) {
Departments.AddRange(Division.SubTaxonomyItems);
});
TaxonomyItem Result = (from d in Departments
where d.Name == DepartmentAbbreviation
select d).FirstOrDefault();
return Result == null ? "" : Result.Title;
}
It first reads all of the Divisons (which there are only 3) but those divisions have many Departments below them as SubTaxonomyItems. Currently I step through each of the Divisions and extract out each of the Departments and put them in a List called Departments. Then I use Linq to search for the specific item.
It works great but I would love to skip/consume that first step of getting the sub items. I have tried the following line that doesn't seem to work:
TaxonomyItem Result = (from d in Departments.SubTaxonomyItems
I then through perhaps some sort of lambda with a foreach of the Departments.SubTaxonomyItems that contains a yeild statement. That may be the trick, but I couldn't get it to work. Looking into the yeild statement it seems there can be a way if I make some extension method. But I am wanting to see if it can be done inline and like the following pseudo code:
public string GetDepartmentTitle(string DepartmentAbbreviation) {
List<TaxonomyItem> Divisions = TaxonomyFromCMS.GetAllItems(DomainDataConstants.DivisionAndDepartment.TAXONOMY_ID);
TaxonomyItem Result = (from d in Divisions.ForEach(delegate(TaxonomyItem Division) {
yeild return Divison.SubTaxonomyItems;
}) AS Dps
where Dps.Name == DepartmentAbbreviation
select Dps).FirstOrDefault();
return Result == null ? "" : Result.Title;
}
Is this possible this way or some other way I am not seeing? Can it even be done without an extension method?

First off, you can solve your problem easily by just adding another "from" to the query:
var query = from division in divisions
from department in division.Departments
where department.Name == whatever
select department;
This does exactly what you were doing; it selects out the sequence of departments from each division, and glues all those sequences together to make one long sequence of departments.
This gives you a slick syntax for the "stitch together a bunch of sequences" scenario. More generally though, sometimes you run into this sort of situation:
var bars = from foo in foos
some complicated query logic here
select foo.bar;
var abcs = from bar in bars
some other query logic here
select bar.abc;
and you want to figure out how to make this into a single query. You can do that like this:
var abcs = from bar in (
from foo in foos
some complicated query logic here
select foo.bar)
some other query logic here
select bar.abc;
which is ugly, or you can do this:
var abcs = from foo in foos
some complicated query logic here
select foo.bar into bar
some other query logic here
select bar.abc;
That does exactly the same thing but it is more pleasant to read. This syntax is called a "query continuation".
To answer your specific question: it is not legal to put a "yield return" in an anonymous method or lambda. This is quite unfortunate because it would be really useful. The transformations that the compiler performs to make anonymous functions and iterator blocks work are quite complex and thus far we have always punted on getting them to work together fully. (That is, you can put a lambda in an iterator block, but you can't put an iterator block in a lambda.) I hope, but do not promise, that some day we'll be able to fix this code up and allow iterator block lambdas. (Remember, Eric's musings about future language features are For Entertainment Purposes Only.)

It looks like you just want something like this.
public string GetDepartmentTitle(string DepartmentAbbreviation) {
var items = TaxonomyFromCMS.GetAllItems(DomainDataConstants.DivisionAndDepartment.TAXONOMY_ID);
var result = items.SelectMany(item=>item.SubTaxonomyItems).FirstOrDefault(item=>item.Name == DepartmentAbbreviation);
var text = result !=null ? result.Title : String.Empty;
return text;
}

Yield return can only be used in very select (pun!) locations, and a Linq query isn't one of them. Luckily you don't need it here.
var q = from division in Divisions
from dps in division.SubTaxonomyItems
where dps.Name == DepartmentAbbreviation
select dps.Title;
return q.FirstOrDefault() ?? String.Empty;

Why not just do:
var divisions = TaxonomyFromCMS.GetAllItems
(DomainDataConstants.DivisionAndDepartment.TAXONOMY_ID);
var titles = from division in divisions
from deparment in division.SubTaxonomyItems
where deparment.Name == DepartmentAbbreviation
select deparment.Title;
return titles.FirstorDefault() ?? "";

Is this linq you are looking for?
var Result = Divisions.SelectMany(d => d.SubTaxonomyItems).Where(subItem => subItem.Name == DepartmentAbbreviation).FirstOrDefault();

Related

Why do I get a different output from analogous Linq queries?

I am trying the following code in LinqPad 5 (specifically 5.26.01)
IEnumerable<string> breeds = new List<string>{
"Fantail",
"Lahore",
"Bokhara Trumpeter",
"Rhine Ringbeater",
"Birmingham Roller",
"Pomeranian Pouter",
"Racing Homer",
"Archangel"};
IEnumerable<string> GetAllBreedsContainingLetter_Fluent(IEnumerable<string> breedlist, string letter)
{
return breedlist
.Where(breedname => breedname.Contains(letter.ToUpperInvariant()) || breedname.Contains(letter.ToLowerInvariant()))
.OrderByDescending(breedname => breedname)
.Select(breedname => breedname);
}
IEnumerable<string> GetAllBreedsContainingLetter_Query(IEnumerable<string> breedlist, string letter)
{
return breedlist = from b in breedlist
where (b.Contains(letter.ToUpperInvariant()) || b.Contains(letter.ToLowerInvariant()))
orderby b descending
select b;
}
var breedsFluent = GetAllBreedsContainingLetter_Fluent(breeds, "R");
breedsFluent.Dump();
var breedsQuery = GetAllBreedsContainingLetter_Query(breeds, "R");
breedsQuery.Dump();
I think the two functions should be analogous but I noticed something odd about the output in Linqpad. The first .Dump() is identified as an IEnumerable<String>; the second .Dump() identifies as a IOrderedEnumerable<String>.
Is this something about the queries I'm running or is it an artifact of Linqpad? I haven't found anything from Googling.
In query syntax the transformation is such that when you have a trivial projection (projecting an item to itself) it only generates a Select call when that trivial projection is the only operation in the query. Since your query contains other operations, the Select is elided entirely, for performance reasons.
A proper translation of that query syntax query into method syntax would skip the Select. The way to replicate the behavior using query syntax would require something like a second query, to do the trivial projection.

C# and LINQ - arbitrary statement instead of let

Let's say I'm doing a LINQ query like this (this is LINQ to Objects, BTW):
var rows =
from t in totals
let name = Utilities.GetName(t)
orderby name
select t;
So the GetName method just calculates a display name from a Total object and is a decent use of the let keyword. But let's say I have another method, Utilities.Sum() that applies some math on a Total object and sets some properties on it. I can use let to achieve this, like so:
var rows =
from t in totals
let unused = Utilities.Sum(t)
select t;
The thing that is weird here, is that Utilities.Sum() has to return a value, even if I don't use it. Is there a way to use it inside a LINQ statement if it returns void? I obviously can't do something like this:
var rows =
from t in totals
Utilities.Sum(t)
select t;
PS - I know this is probably not good practice to call a method with side effects in a LINQ expression. Just trying to understand LINQ syntax completely.
No, there is no LINQ method that performs an Action on all of the items in the IEnumerable<T>. It was very specifically left out because the designers actively didn't want it to be in there.
Answering the question
No, but you could cheat by creating a Func which just calls the intended method and spits out a random return value, bool for example:
Func<Total, bool> dummy = (total) =>
{
Utilities.Sum(total);
return true;
};
var rows = from t in totals
let unused = dummy(t)
select t;
But this is not a good idea - it's not particularly readable.
The let statement behind the scenes
What the above query will translate to is something similar to this:
var rows = totals.Select(t => new { t, unused = dummy(t) })
.Select(x => x.t);
So another option if you want to use method-syntax instead of query-syntax, what you could do is:
var rows = totals.Select(t =>
{
Utilities.Sum(t);
return t;
});
A little better, but still abusing LINQ.
... but what you should do
But I really see no reason not to just simply loop around totals separately:
foreach (var t in totals)
Utilities.Sum(t);
You should download the "Interactive Extensions" (NuGet Ix-Main) from Microsoft's Reactive Extensions team. It has a load of useful extensions. It'll let you do this:
var rows =
from t in totals.Do(x => Utilities.Sum(x))
select t;
It's there to allow side-effects on a traversed enumerable.
Please, read my comment to the question. The simplest way to achieve such of functionality is to use query like this:
var rows = from t in totals
group t by t.name into grp
select new
{
Name = t.Key,
Sum = grp.Sum()
};
Above query returns IEnumerable object.
For further information, please see: 101 LINQ Samples

error in calling and calculate function with int? as input

I have a function like that :
public int? calculateContractPrice(int? comid)
{
int? sum = 0;
var q = from i in dbconnect.tblMaterialGroups
where i.tenderId == _tenderId
select i.id;
foreach (int i in q )
{
var q2 = from g in dbconnect.tblMaterialTenderAnnouncePrices
where g.MaterialGroupId == i && g.companyId == comid
select g;
sum = q2.First().amount*q2.First().price + q2.First().amount*q2.First().PriceForElse + sum;
}
return sum ;
}
When i try to execute this :
List<presentationcontract> q = (from i in dbconnect.tblContracts
where i.tender == _tenderId
select new presentationcontract()
{
tax =(calculateContractPrice(i.companyId)*(6/100)).ToString()
}).ToList();
Tax is string .after executing i got this error :
couldn't translate expression calculateContractPrice(i.companyId)*(6/100),invoke(value(system.Func1[system.nullable1[system.Int32]]))).ToString() into SQL and could not treat it as a local expression
Your edit makes clear the issue. You're trying to do
tax =(calculateContractPrice(i.companyId)*(6/100)).ToString()
in a sql statement but calculateContractPrice is in c#! To understand what's going on you really need to understand a bit how LINQ works.
First of all, stop using the silly sql-style syntax for LINQ. It is less powerful than the lambda syntax and hides what is going on under the hood in a way that makes it hard to understand.
Second consider a LINQ statement
users.Where(u => u.Name == "George").ToList();
where users is IEnumerable<User>. What happens here is that the lambda part is of type Func<User, bool> and gets compiled to a method that gets run against every single instance of User.
Now consider this LINQ statement
db.Users.Where(u => u.Name == "George").ToList();
where db.Users is IQueryable<T>. This looks the same but what happens is VERY different. What happens is that lambda is actually of type Expression<Func<User, bool>> this doesn't get compiled to a method, instead it gets compiled into something called an expression tree. This gets passed to the LINQ provider (in your case Entity Framework I'm guessing) which examines it and converts that into a SQL statement
SELECT Id, Name, Address FROM users WHERE Name = 'George'
What is happening in your case is that it sees the call to calculateContractPrice and simply has no way of converting that to SQL.
What you should therefore do is ensure the query runs first, then use the IEnumerable<T> form of LINQ that runs in c# to call your method.
var contracts = dbconnect.tblContracts.Where(i => i.tender == _tenderId)
.ToList() //Query executes here, now you have IEnumerable<T>
.Select(i => new PresentationContract {
Tax = ...
}).ToList(); //this ToList is only necessary if you want to prevent multiple iteration
You will want to solve all the other problems everyone else pointed out as well of course.
A few other notes - you will want to read up on .Net naming conventions. Usually, anything public,protected, or internal (classes, fields, properties, etc.) is recommended to be PascalCase. Also you probably want to move the division portion into the PresentationContract class. This class that has a Tax property should probably be the one that knows how to generate it.
Try this:
int? ret = calculateContractPrice(i.companyId);
if(ret.HasValue)
{
tax =(ret.Value*(6/100)).ToString();
}
You should make sure that the function indeed returned a value and then you use that integer value in calculation.

Dynamic Linq - Joining a table with 1 to many relationship

I'm using Dynamic Linq as the backend to an in-app reporting tool and I've hit a problem that I can't get round where I have to access a table that has a 1:M relationship .
My simplified data structure is this:
If I were querying this in standard Linq I'd write the query as:
from a in context.Table_A
select new
{
a.RefNo,
val = from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == "A0001"
select(b.Value).FirstOrDefault()
}
This works without any problem. However, when I try the query using Dynamic Linq I can't get the join to work.
From the code below you can see what I'm getting at but obviously I can't use the "a." and the "a.Table_B" references in the query. What do I have to do to be able to access Table_B in this context?
string select = "new (Ref_No,
val = from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == \"A0001\"
select(b.Value).FirstOrDefault()";
var results = context.Table_A.Select(select);
Edit 1:
To answer #Hogan's comment - Why don't I use join: The reports system is dynamic and the select statement may or may not be joining on to Table_B (or indeed joining on to Table_B multiple times) so the join has to be optional. My other issue with this is that unlike the Select method where I can pass in a string as a parameter (allowing me to make it dynamic quite easily) the Join() method can't be called in that way. The closest thing I've found is a dynamic Linq join extention method, something I may have to consider using but I've a feeling that this will be cumbersome with the dynamic select().
Edit 2:
Based on Hogan's suggestions I've got this far:
delegate string searchTableA(Table_A a);
public void Search()
{
....
searchTableA sel = (a) =>
{
return (from b in context.Table_B
where (b.A_ID == a.ID)
select (b.Value)).FirstOrDefault();
};
var res = context.Table_A.Select(sel);
}
This gives the error: 'System.Data.Entity.DbSet<TestDynamicLinqJoins.Table_A>' does not contain a definition for 'Select' and the best extension method overload 'System.Linq.Dynamic.DynamicQueryable.Select(System.Linq.IQueryable, string, params object[])' has some invalid arguments
Hard to give exact code because I don't know the types of your elements, but something like this would work fine using delegates.
delegate string searchTableA(elementType a);
searchTableA sel = (a) =>
{
return from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == "A0001")
select(b.Value).FirstOrDefault();
};
var results = context.Table_A.Select(sel);

How to use the return value of a function in a linq where clause?

I'm trying to create a general search query against an EF entity type (person). In general, the search takes a string, splits it by commas, and then searches for people whose various attributes contain all of the key words.
I have a function called getProperties(Person p) that takes an entity (overridden by entity type), and returns a string of the various relevant properties joined together with a delimiter... such as:
John~Doe~Team A~Full Time
If the user searches for "Team A, Full" person corresponding to the above flattened entity should be returned... however, if the enter "John, Smith" it shouldn't.
I think the following looks right, but it just doesn't work...
public IEnumerable<Person> SearchPeople(string searchString)
{
if (searchString == null || string.IsNullOrEmpty(searchString.Trim()))
return base._objectSet.ToList();
string[] SearchWords = searchString.Split(',').Select(s => s.Trim()).ToArray();
return (from person
in base._objectSet
let t = (getProperties(person))
where SearchWords.All(word => t.Contains(word))
select person).ToList();
}
and the getProperties function is:
public static string getProperties(Person p)
{
string[] values = { p.Surname, p.GivenName, p.Team, p.Status };
return values.Aggregate((x, y) => String.IsNullOrEmpty(y) ? x : string.Concat(x, "~", y));
}
Does anyone see where I'm going wrong?
Edit
No exceptions are raised, but when I step through the code, when I get to the linq, it steps into the dispose method of the unitofwork that is hosting the query. Very odd.
If I change it so that it searches against a hard-coded string, it works as expected:
var test = (from person
in base._objectSet
where SearchWords.All(word => "John~Doe~Team A~Full Time".Contains(word))
select person).ToList();
well, it works in that it matches the queries I expect it to, but as it's static, it returns every person record (pretty much like having where(true) =P)
Edit the Second
Even odder is that if I store the results into a var, then return the var with a breakpoint on the return, execution never hits the breakpoint... this linq is like a black hole... I can step into it, but it never returns me back to my SearchPeople method... it just disposes the context and forgets about it.
Edit the Third
If I don't call ToString() right away and look at the linq expression in debugger, it says "Linq to Entities does not recognize the method getProperties(Person)" Looks like it was silently choking on my method... any way to use my method without linq choking on it?
You are returning a List? try the return type to be a List or change the .ToList() to be AsEnumerable()
public List<Person> SearchPeople(string searchString)
{
if (searchString == null || string.IsNullOrEmpty(searchString.Trim()))
return base._objectSet.ToList();
string[] SearchWords = searchString.Split(',').Select(s => s.Trim()).ToArray();
return (from person
in base._objectSet
let t = (getProperties(person))
where SearchWords.All(word => t.Contains(word))
select person).ToList();
}
Well, after finding out that linq 2 entites doesn't like methods (as is doesn't know how to translate it to sql), i rewrote my linq is a very tedious but functioning manner:
var people = from p
in base._objectSet
where SearchWords.All(p.GivenName.Contains(word) || p.Surname.Contains(word) || p.(???).Contains(word) || etc.)
select p;
Annoying, but there you go.

Categories

Resources