I'm not a C# programmer, nor did I design the database for this, but I've been tasked with creating a Linq query that doesn't know what columns to use at compile time. Trying to be succinct, here's the gist of what I'm trying to do:
From the frontend, the user can select one or more groups and expect back a JSON string containing those users that will fill up a paginated table.
Let's say I have these groups:
Group1
Group2
Group3
Group4
and the user selects Group1, Group2, and Group4. They expect back from the database users that belong to any one of these groups.
In the database, these group names are column names.
If I were to write a straight SQL statement it would look something like this:
SELECT EmailAddress, FirstName, LastName
FROM Contacts AS C
JOIN GroupContacts AS D
ON C.ID = D.ContactId
WHERE D.DealId = 'some unique id'
AND (
D.Group1 = 1
OR
D.Group2 = 1
OR
D.Group4 = 1
)
ORDER BY C.LastName
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY
Where the = 1 means that Contact is in that group.
All I want to be able to do is to make that AND (...) part dynamic, depending on what the user has selected.
I've investigated how to do it with C# Expressions, but the functions I'm coming across (Expression.OrElse()) can only take two parameters.
What is my approach here?
EDIT:
Here is my sad C# code:
String[] GroupNames = { "Group1", "Group2", "Group4" }; // user selected groups
List<Expression> expressionList = new List<Expression>();
foreach (String name in GroupNames)
{
expressionList.Add(Expression.Equal(Expression.Constant(name), 1));
}
Here is where I'm really not sure what to do. I think I could write the rest of the expression if I could just get this expressionList to evaluate.
I know that I will have a finite number of groups. At first I tried writing a switch statement sort of solution:
List<Expression> expressionList = new List<Expression>();
Expression expressionOR = Expression.Empty();
Expression rightSide = Expression.Constant(true, typeof(bool));
foreach (string name in GroupNames)
{
switch (name)
{
case "Group1":
leftSide = Expression.Constant("Group1");
e1 = Expression.Equal(leftSide, rightSide);
break;
case "Group2":
leftSide = Expression.Constant("Group2");
e2 = Expression.Equal(leftSide, rightSide);
break;
case "Group3":
leftSide = Expression.Constant("Group3");
e3 = Expression.Equal(leftSide, rightSide);
break;
case "Group4":
leftSide = Expression.Constant("Group4");
e4 = Expression.Equal(leftSide, rightSide);
break;
}
expressionOR = Expression.Equal(leftSide, rightSide);
expressionList.Add(expressionOR);
}
And then I would try to use Expression.OrElse(expressionList); is some way.
Forgive my naivety on C#, and thanks so far to everybody who has responded. It's really appreciated.
You don't post a lot of code to go on but if you're just trying to use expressions to dynamically add conditions you don't need to. You could do something like:
where DealId == "some unique id"
&& ((GroupNames.Contains("Group1") && Group1 = 1) ||
(GroupNames.Contains("Group2") && Group2 = 1) ||
(GroupNames.Contains("Group3") && Group3 = 1) ||
(GroupNames.Contains("Group4") && Group4 = 1))
Need using System.Linq.Dynamic NuGet relation
Sample:
var gropString = String.Format("new ({0})", GroupDocumentByFields().ToSeparatedString());
var groupping = list1.AsQueryable().GroupBy(gropString, "it");
foreach (IGrouping<dynamic, ITAP.Database.OrderItems> items in groupping) {
var idorders = items.Select(p => p.IDOrder).Distinct().ToList();
var goods = listGoods.Where(p => idorders.Contains(p.IDOrder));
var services = listServices.Where(p => idorders.Contains(p.IDOrder));
...
protected List<string> GroupDocumentItemsByFields() {
var fields = new List<string>();
if (GetNameVariant == Selling.GetNameVariant.OrderItemsName)
fields.Add("Name as ItemName");
if (GetNameVariant == Selling.GetNameVariant.OrderItemsShortName)
fields.Add("ShortName as ItemName");
if (GetNameVariant == Selling.GetNameVariant.SellingName)
fields.Add("SellingName as ItemName");
if(this.PriceVariant == Selling.PriceVariant.PriceWithoutNDS)
fields.Add("PriceWithoutNDS as Price");
if (this.PriceVariant == Selling.PriceVariant.Price)
fields.Add("Price as Price");
return fields;
}
Related
I need to select 2 cities in a table with linq:
SOLUTION 1: just one query
var CityQuery = db.Cities.Where(c => c.CityId == City1Id || c.CityId == City2Id).Take(2)
foreach (var item in CityQuery)
{
if (item.CityId == City1Id)
{
City1Name = item.CityName;
}
else
{
City2Name = item.CityName;
}
}
or
SOLUTION 2: execute 2 queries
var City1Query = db.Cities.Where(c => c.CityId == City1Id).FirsOrDefault();
City1Name = City1Query.CityName;
var City2Query = db.Cities.Where(c => c.CityId == City2Id).FirsOrDefault();
City2Name = City2Query.CityName;
Which query is the most efficient ? What is the best practice ?
Generally speaking, your Solution 1 should be faster since it makes a single round-trip to the database. However, whether that difference is significant or not depends on your use case.
Here's an alternative solution that only brings back the city names from the database (vs bringing back all columns). At first look, this might look like an inefficient cartesian product but the db engine will most likely optimize this, especially if there exist an index on the CityId column.
var result = from city1 in CityQuery where city1.CityId == City1Id
from city2 in CityQuery where city2.CityId == City2Id
select new
{
City1Name = c1.CityName,
City2Name = c2.CityName
};
City1Name = result.City1Name;
City2Name = result.City2Name;
I am just learning LINQ and I have come across and issue Im not sure how to do in LINQ.
string numbers = "1,3,4,5";
string[] outletsInaStringArray = outlets.Split(',');
List<string> numbersAsAList = outletsInaStringArray.ToList();
I have a field in my database which holds a number. I only want to select the lines WHERE the number in the database is IN the line list of numbers "1,3,4,5" (these numbers are just examples).
Thanks in advance
I have looked at Tim and James answers and also looked at the line that James has sent. Im still a bit confused.....Sorry. Below is my actual code. It compiles but does not work
string outlets = "1,3,4,5"
string[] outletsNeeded = outlets.Split(',');
List<string> outletsNeededList = outletsNeeded.ToList();
DashboardEntities1 db = new DashboardEntities1();
var deptSalesQuery = (
from d in db.DashboardFigures
where (d.TypeOfinformation == "DEPTSALES") && (outletsNeeded.ToString().Contains(d.OutletNo.ToString()))
select new DeptSales
{
Dn = (int)d.Number,
Dnm = "Mens",
On = d.OutletNo,
Qs = (double)d.Value_4,
Se = (double)d.Value_2,
Si = (double)d.Value_3
}
);
In the DASHBAORDFIGURES table in SQL I have 2 records where the outlets number = 1, and therefore should have come up with two records.
Sorry if this is a simple thing, its just new to me and its frustrating.
You can use Contains as tagged:
var query = db.Table
.Where(x => outletsInaStringArray.Contains(x.Number) && x.information == "SALES");
that was method syntax, if you prefer query syntax:
var query = from figure in db.Figures
where outletsInaStringArray.Contains(figure.number)
&& figure.information == "SALES"
select figure;
But the column number is int, the List<string> stores strings, maybe your LINQ provider does not support .Contains(figure.ToString()). Then convert the strings to int first:
List<int> outletsNeededList = outletsNeeded.Select(int.Parse).ToList();
The answer that Tim provided is one method. Linq and lambda are interchangeable. Have a look at the following posting as well. Link
var result = from x in db.Table.ToList()
where outletsInaStringArray.Contains(x.Number)
select x;
Also have a look the following as it offers a very similar solution to the one you are looking for:
Link
As per i understand, you want to fetch data in similar way as IN (SQL) clause does it.
SELECT <Field_List>
FROM Table
WHERE IntegerField IN (1,2,4,5)
But i'm wondering why do you want to do it that way, when you can join data and get only matches. The worse is that you're trying to mix different data type and pass comma delimited text as a set of integers (i may be wrong):
SELECT <Field_List>
FROM Table
WHERE IntegerField IN ("1,2,4,5")
Above query won't execute, because the set of integers is "packed" into comma delimited string. To be able to execute that query, a conversion between data types must be done. Numbers in a string have to be converted to a set of integers (using user define split function or Common Table Expression):
;WITH CTE AS
(
--here convertion occurs
)
SELECT t2.<Field_List>
FROM CTE As t1 INNER JOIN TableName AS t2 ON t1.MyNumber = t2.IntegerField
Linq + any programming language is more flexible. You can build a list of integers (List) to build query.
See simple example:
void Main()
{
List<MyData> data = new List<MyData>{
new MyData(1,10),
new MyData(2, 11),
new MyData(5, 12),
new MyData(8, 13),
new MyData(12, 14)
};
//you're using comma delimited string
//string searchedNumbers = "1,3,4,5";
//var qry = from n in data
// join s in searchedNumbers.Split(',').Select(x=>int.Parse(x)) on n.ID equals s
// select n;
//qry.Dump();
List<int> searchedNumbers = new List<int>{1,2,4,5};
var qry = from n in data
join s in searchedNumbers on n.ID equals s
select n;
qry.Dump();
}
// Define other methods and classes here
class MyData
{
private int id = 0;
private int weight = 0;
public MyData(int _id, int _weight)
{
id = _id;
weight = _weight;
}
public int ID
{
get{return id;}
set {id = value;}
}
public int Weight
{
get{return weight;}
set {weight = value;}
}
}
Result:
ID Weight
1 10
5 12
Cheers
Maciej
Thank you all iv now got it to work using all your suggestions
the final code that works is as follows
DeptSales myDeptSales = new DeptSales(); // Single department
List<DeptSales> myDeptSalesList = new List<DeptSales>(); // List of Departments
DashboardEntities1 db = new DashboardEntities1();
var deptSalesQuery = from d in db.DashboardFigures
join s in outlets.Split(',').Select(x => int.Parse(x)) on d.OutletNo equals s
where (d.TypeOfinformation == "DEPTSALES")
select new DeptSales
{
Dn = (int)d.Number,
Dnm = "Mens",
On = d.OutletNo,
Qs = (double)d.Value_4,
Se = (double)d.Value_2,
Si = (double)d.Value_3
};
Thanks once again.
I have problem I've been trying to solve for the last couple of hours but without any luck at all.
First, let me show you how my database looks like (just an important part of it):
[radno_mjesto] = JOB
[grupa_radnih_mjesta] = JOB GROUPS (jobs that fall into specific groups; for example if the group name was judges jobs that would fall into this group would be: supreme court judge, administrative law judge, senior judge, etc.)
[osoba] = PERSON
What I'd like to achieve is to query all the people who fall into specific job groups, but after couple of hours I wasn't able to succeed in doing it so. I was trying various combinations of the following code and there are only 2 results I've been getting: all people (no matter what their job is) or people with specific job only (the last job from the job group as in this case).
var sveOsobe = from p in db.osobas
select p;
if (chkGrupaRadnihMjesta.Checked)
{
int id = Convert.ToInt32(GrupaRadnihMjesta.SelectedValue);
var radnaMjesta = from rm in db.grupe_radnih_mjesta_radna_mjesta
where rm.grm_id == id
select rm;
var praznoOsobe = sveOsobe.Where(o => o.osoba_id == -1);
foreach (var radnoMjesto in radnaMjesta)
{
var sveOsobeRadnaMjesta = from p in db.osobas
where p.osoba_id == -1
select p;
sveOsobeRadnaMjesta = sveOsobe.Where(o => o.rm_id == radnoMjesto.rm_id).Union(sveOsobeRadnaMjesta);
praznoOsobe = praznoOsobe.Union(sveOsobeRadnaMjesta);
}
sveOsobe = sveOsobe.Intersect(praznoOsobe);
}
Any help would be appreciated.
This should work....
if (chkGrupaRadnihMjesta.Checked) {
int id = Convert.ToInt32(GrupaRadnihMjesta.SelectedValue);
var sveOsobe = (
from p in db.osobas
join l in db.grupe_radnih_mjesta_radna_mjesta on l.rm_id equals p.rm_id
where l.grm_id == id
select p
).Distinct();
}
I'm guessing at names here!!!
I'm using a Linq to SQL query to provide a list of search term matches against a database field. The search terms are an in memory string array. Specifically, I'm using an "intersect" within the Linq query, comparing the search terms with a database field "Description". In the below code, the description field is iss.description. The description field is separated into an array within the Linq query and the intersect is used to compare the search terms and description term to keep all of the comparing and conditions within the Linq query so that the database is not taxed. In my research, trying o overcome the problem, I have found that the use of an in-memory, or "local" sequence is not supported. I have also tried a few suggestions during my research, like using "AsEnumerable" or "AsQueryable" without success.
searchText = searchText.ToUpper();
var searchTerms = searchText.Split(' ');
var issuesList1 = (
from iss in DatabaseConnection.CustomerIssues
let desc = iss.Description.ToUpper().Split(' ')
let count = desc.Intersect(searchTerms).Count()
where desc.Intersect(searchTerms).Count() > 0
join stoi in DatabaseConnection.SolutionToIssues on iss.IssueID equals stoi.IssueID into stoiToiss
from stTois in stoiToiss.DefaultIfEmpty()
join solJoin in DatabaseConnection.Solutions on stTois.SolutionID equals solJoin.SolutionID into solutionJoin
from solution in solutionJoin.DefaultIfEmpty()
select new IssuesAndSolutions
{
IssueID = iss.IssueID,
IssueDesc = iss.Description,
SearchHits = count,
SolutionDesc = (solution.Description == null)? "No Solutions":solution.Description,
SolutionID = (solution.SolutionID == null) ? 0 : solution.SolutionID,
SolutionToIssueID = (stTois.SolutionToIssueID == null) ? 0 : stTois.SolutionToIssueID,
Successful = (stTois.Successful == null)? false : stTois.Successful
}).ToList();
...
The only way I have been successful is to create two queries and calling a method as shown below, but this requires the Linq Query to return all of the matching results (with the number of hits for search terms in the description) including the non-matched records and provide an in-memory List<> and then use another Linq Query to filter out the non-matched records.
public static int CountHits(string[] searchTerms, string Description)
{
int hits = 0;
foreach (string item in searchTerms)
{
if (Description.ToUpper().Contains(item.Trim().ToUpper())) hits++;
}
return hits;
}
public static List<IssuesAndSolutions> SearchIssuesAndSolutions(string searchText)
{
using (BYCNCDatabaseDataContext DatabaseConnection = new BYCNCDatabaseDataContext())
{
searchText = searchText.ToUpper();
var searchTerms = searchText.Split(' ');
var issuesList1 = (
from iss in DatabaseConnection.CustomerIssues
join stoi in DatabaseConnection.SolutionToIssues on iss.IssueID equals stoi.IssueID into stoiToiss
from stTois in stoiToiss.DefaultIfEmpty()
join solJoin in DatabaseConnection.Solutions on stTois.SolutionID equals solJoin.SolutionID into solutionJoin
from solution in solutionJoin.DefaultIfEmpty()
select new IssuesAndSolutions
{
IssueID = iss.IssueID,
IssueDesc = iss.Description,
SearchHits = CountHits(searchTerms, iss.Description),
SolutionDesc = (solution.Description == null)? "No Solutions":solution.Description,
SolutionID = (solution.SolutionID == null) ? 0 : solution.SolutionID,
SolutionToIssueID = (stTois.SolutionToIssueID == null) ? 0 : stTois.SolutionToIssueID,
Successful = (stTois.Successful == null)? false : stTois.Successful
}).ToList();
var issuesList = (
from iss in issuesList1
where iss.SearchHits > 0
select iss).ToList();
...
I would be comfortable with two Linq Queries, but with the first Linq Query only returning the matched records and then maybe using a second, maybe lambda expression to order them, but my trials have not been successful.
Any help would be most appreciated.
Ok, so after more searching more techniques, and trying user1010609's technique, I managed to get it working after an almost complete rewrite. The following code first provides a flat record query with all of the information I am searching, then a new list is formed with the filtered information compared against the search terms (counting the hits of each search term for ordering by relevance). I was careful not to return a list of the flat file so there would be some efficiency in the final database retrieval (during the formation of the filtered List<>). I am positive this is not even close to being an efficient method, but it works. I am eager to see more and unique techniques to solving this type of problem. Thanks!
searchText = searchText.ToUpper();
List<string> searchTerms = searchText.Split(' ').ToList();
var allIssues =
from iss in DatabaseConnection.CustomerIssues
join stoi in DatabaseConnection.SolutionToIssues on iss.IssueID equals stoi.IssueID into stoiToiss
from stTois in stoiToiss.DefaultIfEmpty()
join solJoin in DatabaseConnection.Solutions on stTois.SolutionID equals solJoin.SolutionID into solutionJoin
from solution in solutionJoin.DefaultIfEmpty()
select new IssuesAndSolutions
{
IssueID = iss.IssueID,
IssueDesc = iss.Description,
SolutionDesc = (solution.Description == null) ? "No Solutions" : solution.Description,
SolutionID = (solution.SolutionID == null) ? 0 : solution.SolutionID,
SolutionToIssueID = (stTois.SolutionToIssueID == null) ? 0 : stTois.SolutionToIssueID,
Successful = (stTois.Successful == null) ? false : stTois.Successful
};
List<IssuesAndSolutions> filteredIssues = new List<IssuesAndSolutions>();
foreach (var issue in allIssues)
{
int hits = 0;
foreach (var term in searchTerms)
{
if (issue.IssueDesc.ToUpper().Contains(term.Trim())) hits++;
}
if (hits > 0)
{
IssuesAndSolutions matchedIssue = new IssuesAndSolutions();
matchedIssue.IssueID = issue.IssueID;
matchedIssue.IssueDesc = issue.IssueDesc;
matchedIssue.SearchHits = hits;
matchedIssue.CustomerID = issue.CustomerID;
matchedIssue.AssemblyID = issue.AssemblyID;
matchedIssue.DateOfIssue = issue.DateOfIssue;
matchedIssue.DateOfResolution = issue.DateOfResolution;
matchedIssue.CostOFIssue = issue.CostOFIssue;
matchedIssue.ProductID = issue.ProductID;
filteredIssues.Add(matchedIssue);
}
}
Is there a way to write a query in LINQ to return the count of the search terms found in a field(s)
Basically, I want this to work:
var matches = from t in _db.Books
let score = GetScore(t, searchterms)
where score >= 1
orderby score descending
select t;
public static int GetScore(Book b, params string[] searchterms)
{
int count = 0;
foreach (string term in searchterms)
{
if (b.Title.Contains(term))
count++;
}
return count;
}
But, of course, that can't work.
Can my little GetScore function be translated into LINQ?
Thanks.
EDIT: I would also prefer to have the score accessible. Ideally I will be selecting my results into a SearchResults class (for the View) that would contain some Book info and the Book's score from the query. To update my query, it'd be something like this:
var matches = from t in _db.Books
let score = GetScore(t, searchterms)
where score >= 1
orderby score descending
select new SearchResult
{
Title = t.Title,
Type = "Book",
Link = "Books/Details/" + t.BookID,
Score = score
};
I'm sorry I wasn't more clear originally.
You can't do what you want to do without issuing multiple queries to the database - essentially one per search term. If you are happy to do that, then here is an easy way to do it:
var terms = new [] { "s", "t", "r", "e", "b", "c", };
var ids =
from term in terms
from id in _db.Books
.Where(book => book.Title.Contains(term))
.Select(book => book.Id)
group term by id into gts
orderby gts.Count() descending
select gts.Key;
var selectedIds = ids.Take(50).ToArray();
var query =
from book in _db.Books
where selectedIds.Contains(book.Id)
select book;
I wrote the ids to return a list of ids sorted by those that match the most terms first. This was to most closely get the same kind of result that you wanted in your question. I then decided to use a Take(50) to get the top 50 results. You can obviously change this strategy to suit your needs, but you must end up with an array of ids to use in the final query.
I hope this helps.
EDIT: based on OP's edit.
Here's how to query with the score included:
var terms = new [] { "s", "t", "r", "e", "b", "c", "l", "i", };
var idScores =
from term in terms
from id in _db.Books
.Where(book => book.Title.Contains(term))
.Select(book => book.BookID)
group term by id into gts
select new
{
Id = gts.Key,
Score = gts.Count(),
};
var selectedIds = idScores.Select(x => x.Id).Take(50).ToArray();
var selectedBooks =
from book in _db.Books
where selectedIds.Contains(book.BookID)
select book;
var query =
from b in selectedBooks.ToArray()
join x in idScores on b.BookID equals x.Id
orderby x.Score descending
select new
{
Title = b.Title,
Type = "Book",
Link = "Books/Details/" + b.BookID,
Score = x.Score,
};
If you want to convert your GetScore() function in LINQ then your can change whole LINQ to this:
var matches = from t in _db.Books
where searchterms.Count(c => c == t.Title) >= 1
orderby searchterms.Count(c => c == t.Title)
select t;
Now it will compile successfully but on run time when you will bind this matches to grid or any where it will throw exception "Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator."
Because "problem is that we are trying to join an SQL table and an in-
memory list. Since you write the query against the SQL table, it goes
through LINQ to SQL, which rightly complains that it cannot do that.
so If you really want to do an in-memory join, then you must use _db.Books.AsEnumerable()
then query will be:
var matches = from t in _db.Books.AsEnumerable()
where searchterms.Count(c => c == t.Title) >= 1
orderby searchterms.Count(c => c == t.Title)
select t;
You have two options:
Do the search in the DB via a Stored procedure that returns a pair (BookId, Score), and then use that to do the query in LINQ2SQL
Use ToList() to execute the query, and avoid getting the "Local sequence cannot be used..." error.
For the second option, the query (in lambda syntax) would be something like
db.Books
.ToList()
.Select(t=> new SearchResult {
Title = t.Title,
Type = "Book",
Link = "Books/Details/" + t.BookID,
Score = GetScore(t, searchTerms)
})
.Where(t => t.Score >=1);
In the latter case, you'll be bringing the whole Book table to memory (and using LINQ2Objects to do the filtering), so I'd rather go for the first one.
if you want to get filtered data tou may build Expression by your self. I make some examle for your task, but you need to improve it for your source, or create some global improvements
let's start from the end. I can't make Expression returned anonymous Type so I create Generic wich I am planning to use as result
public class GRes<T>
{
public T Key { get; set; }
public int Count { get; set; }
}
I'm going to make grouping by any field of object or the object itself, and then on this grouping results call Sum fnction with some lambda which look's like this
x=>0 + IIF(x.Code.Contains(p1), 1, 0) + IIF(x.Code.Contains(p2), 1, 0) ...
where Code - is some field and p1,p2... - your search terms
at last call would be
IQueriable<GRes<Book>> result = context.Books
.GroupBy(d => d).CountIn(searchTerms, "Code")
.Where(r => r.Count > 0)
.OrderByDescending(r => r.Count);
CountIn - is extention :
public static IQueryable<GRes<TKey>> CountIn<TKey, TValue>(this IQueryable<IGrouping<TKey, TValue>> source, IEnumerable<string> values, Expression<Func<TValue,string>> selector)
{
ParameterExpression xExpr = selector.Parameters[0];
Expression propExpr = selector.Body;
MethodInfo mi = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
Expression res = Expression.Constant(0);
foreach (string term in values)
{
Expression value = Expression.Constant(term);
MethodCallExpression methodEpr = Expression.Call(propExpr, mi,value);
Expression tx = Expression.Condition(methodEpr, Expression.Constant(1), Expression.Constant(0));
res = Expression.Add(res, tx);
}
var r0 = Expression.Lambda<Func<pp_Disease, int>>(res, xExpr);
Type groupingType = typeof(IGrouping<TKey, TValue>);
ParameterExpression selPar = Expression.Parameter(groupingType, "i");
MethodInfo mi1 = typeof(Enumerable).GetMethods()
.FirstOrDefault(m => m.Name == "Sum"
&& m.ReturnParameter.ParameterType == typeof(int)
&& m.GetParameters().Count() == 2)
.MakeGenericMethod(typeof(pp_Disease));
Expression r1 = Expression.MemberInit(Expression.New(typeof(GRes<TKey>))
, Expression.Bind(typeof(GRes<TKey>).GetMember("Count")[0], Expression.Call(mi1, selPar, r0))
, Expression.Bind(typeof(GRes<TKey>).GetMember("Key")[0], Expression.Property(selPar, "Key")));
return source.Select(Expression.Lambda<Func<IGrouping<TKey, TValue>, GRes<TKey>>>(r1, selPar));
}
and when this function would be called your get SQL like this:
SELECT
...
FROM ( SELECT
...
FROM ( SELECT
....
SUM([Extent1].[A1]) AS [A1]
FROM ( SELECT
...
0 + (CASE WHEN ([Extent1].[Code] LIKE N'%2%') THEN 1 ELSE 0 END) + (CASE WHEN ([Extent1].[Code] LIKE N'%I%') THEN 1 ELSE 0 END) AS [A1]
FROM (SELECT
...
FROM [dbo].[pp_Disease] AS [pp_Disease]) AS [Extent1]
) AS [Extent1]
GROUP BY [K1], [K2], [K3], [K4]
) AS [GroupBy1]
WHERE [GroupBy1].[A1] > 0
) AS [Project1]
ORDER BY [Project1].[C1] DESC
i removed some fields declarations (EF generated sql huge), most important for this example is case string with parameters that we put in our function
i took your problem as below
books.Add("Robin HOOD");
books.Add("Charles");
books.Add("James");
search.Add("Rob");
search.Add("ood");
search.Add("les");
search.Add("am");
so you want the count of search items which are found in any of the books so if run this example , you will get the correct result.
var temp = searchterms.Where(x=>books.Where(y=>y.Title.Contains(x)).Count()>0);
var matches = from t in _db.Books
let score = searchterms.Where(term => t.Title.Contains(term)).Count()
where score >= 1
orderby score descending
select t;
edit
if its linq to sql i think my solution is devide into 2 part
// just results in db
string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
var results = from t in _db.Books
where terms.Contains(t.Title.Contains)
select t;
// sort results in c#
var sorting = for entry in results
let score = searchterms.Where(term => entry.Title.Contains(term)).Count()
orderby score
select new {......};
edit
oooh sorry little mistake. on first query
string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
var results = from t in _db.Books
where terms.Contains(t.Title.Contains)
select t;
change to
string terms = searchterms.Aggregate((cur,nex) => cur+"^"+nex);
var results = from t in _db.Books
where terms.Contains("^" + t.Title + "^")
select t;
the reason i use this query is 'var results' will gives all the result from db which should be ok becus it's string.Contains. Then sort the result in next query.