I'm a newcomer in Linq C#.
I have a scenario where I need to check part of a sentence is equal to another value of the field.
I use IndexOf to get part of the sentence in the left join condition. The result is good when any data match between 'a' and 'c'. But when data does not exist in 'c', then the value of 'test' is all data of table dbData.Data3.
Can anyone know what I'm missing here?
var test = (from a in dbData.Data2
let COLODescIndexOfSpace = a.LongKeywordDesc .IndexOf(' ') < 0 ? 0 : a.LongKeywordDesc .IndexOf(' ')
join c in dbData.Data3 on
new
{
KeywordDesc = a.LongKeywordDesc .Substring(0, COLODescIndexOfSpace),
Stsrc = true
}
equals new
{
KeywordDesc = c.KeywordDesc,
Stsrc = (c.Stsrc != AppConstant.StrSc.Deactive)
}
into c_leftjoin
from c in c_leftjoin.DefaultIfEmpty()
where lmsCourseOutlineIds.Contains(a.CourseOutlineID)
select new
{
data = a.KeywordID,
data2 = c.KeywordID,
data3 = c.KeywordDesc,
}).ToList();
this is some example of data
here
and this is what I expect as result
here
Here is one way to solve the problem (I will leave it to my Linq betters figure out a solution purely with Linq). In short, just create a SQL View in the database and call it to get your desired results.
For example, in the database:
create view dbo.GetSentencesByKeywords
as
select
d2.ID,
d2.longKeywordDesc,
d3.keywordDesc,
d3.AdditionalInfo
from
dbo.Data2 d2
left join
(
select
ID,
keyWordDesc,
AdditionalInfo,
len(keyWordDesc) as keyWordLength
from dbo.Data3 data3
) d3
on substring(d2.longKeywordDesc, 0, d3.keyWordLength + 1) = d3.keywordDesc
Now the linq is nice and simple (assuming you can re-scaffold the dbContext to add it to your models, or otherwise can get it in there one way or another):
var x = dbData.GetSentencesByKeywords.Select(c => c);
How would you write "SELECT col.a, col.b FROM TABLE WHERE ID = 1" in LINQ to Entity 6 so that you could save col.a into variable A and col.b into variable B. You can do this with SqlReader by retrieving the index or column name, but with LINQ to Entity it is returned as one object. When returning a two field LINQ query to a list, both field are saved to the first index of the list and can only be accessed as the same list element. Is it standard to create two LINQ queries, one for each variable? Or am I missing part of the process?
Current query:
var result = (from col in cxt.Table
where col.ID == "1"
select new {col.a, col.b}).ToList();
If you are expecting exactly one record to return from database then what you can do:
var result = cxt.Table.FirstOrDefault(x => x.ID == "1");
//Notice that might be null if has not result
var a = result.a;
var b = result.b;
Will look very similar in query syntax but I think this method syntax neater in this case
I am not sure what exactly you are looking for. But selecting two variable by Linq is not so hard.
var value = (from ta in db.YourTable
where ta.ID = id
select new {
A = ta.a,
B = ta.b
}).FirstOrDefault();
var a = value.A;
var b = value.B;
If you use ToList() instead of FirstOrDefault(), you will get a list contain zero or more objects. You can simply use a loop to get field from each object.
forach(var value in values)
{
var a = value.A;
var b = value.B;
}
I have a table in my database with four columns:
string: year
string: weeknr
int: number
In other tables I combine these columns into yywwnnn as a string.
The number columns is a identity column.
Now I want to retrieve some records from a table that I want to join with the mentioned table.
something like:
from R in db.Requests
join RN in db.RequestNumbers on R.RequestNumber equals (RN.Year + RN.Weeknr + RN.Number)
But of course RN.Number is a integer and I need it to be a 3 digit string.
so:
16 07 1 ==> 1607001
16 07 10 ==> 1607010
16 07 100 ==> 1607100
I have tried this:
from R in db.Requests
join RN in db.RequestNumbers on R.RequestNumber equals (RN.Year + RN.Weeknr + (RN.Number.toString().PadLeft(3,char.Parse("0")))
But PadLeft is not recognized.
Is there any other solution to this?
[EDIT]
This is the full method:
public List<RequestList> getMyRequests(string userID)
{
var result = (from R in db.Requests
join MT in db.MainTorsList on R.Category equals MT.MainTorsListID into MTL
from MT in MTL.DefaultIfEmpty()
join ST in db.SubTorsList on R.RequestType equals ST.SubTorsListID into STL
from ST in STL.DefaultIfEmpty()
join S in db.StatusList on R.RequestStatus equals S.StatusListID into SL
from S in SL.DefaultIfEmpty()
join RN in db.RequestNumber on R.RequestNumber equals RN.Year + RN.Week + (RN.Number.ToString().PadLeft(3, char.Parse("0"))) into RNL
from RN in RNL.DefaultIfEmpty()
where R.CreatedBy == userID && RN.Removed == false
select new
{
RequestID = R.RequestID,
RequestDate = R.CreatedOn,
RequestNumber = R.RequestNumber,
Category = MT.Name,
RequestType = ST.Name,
Description = R.RequestDescription,
Status = S.Name,
Options = ""
}
);
List<RequestList> requests = (List<RequestList>)result.ToList().ToNonAnonymousList(typeof(RequestList));
return requests;
}
The error message:
Additional information: LINQ to Entities does not recognize the method 'System.String PadLeft(Int32, Char)' method, and this method cannot be translated into a store expression.
The trick is to use DbFunctions.Right like this
DbFunctions.Right("00" + RN.Number, 3)
i.e. prepend enough zeros at the beginning and take the exact length needed from the end.
All the used methods are supported by LINQ to Entities (at least in the latest at the moment EF6.1.3).
When you creates a linq expression pointing to a sql-database this it is translated into a sql query and there are functions that cannot be translated to sql (such as string.Format(), object.ToString()). When an unsupported function is used, an exception like yours is raised.
'SqlFunctions' and 'EntityFunctions' classes provides 'CRL methods' that you can use in Linq to Entities expressions.
SqlFunctions.StringConvert() converts an integer to its string representation, allowing you specify the desired length (filling with leading spaces).
You can use this function and call string.Replace(string, string) method (yes, it's available) to replace spaces to zeros.
This is the query:
from R in db.Requests
join RN in db.RequestNumbers on R.RequestNumber equals
(RN.Year + RN.Weeknr + SqlFunctions.StringConvert((double)RN.Number, 3).Replace(" ", "0"))
Hope this helps.
Did you try:
Rn.number.value.ToString("000", System.Globalization.CultureInfo.InvariantCulture)
?
You can create the string in the needed format and then use it in your expression. Here's a quick example I did to pad '0' depending on the length of the number after the fourth character. You wouldn't need to assign var mystring = "your string" and so forth, I was just doing that so I didn't need to type in all the scenarios each time to run it:
var exampleA = "16071";
var exampleB = "160710";
var exampleC = "1607100";
var mystring = exampleB;
var afterFourthCharacter = mystring.Substring(4);
var result = mystring.Substring(0,4) + afterFourthCharacter.PadLeft(mystring.Length + (3 - mystring.Length), '0');
Create dynamic keys with the value and let Linq sort it out.
Something like this:
db.Requests // Outer
.Join(db.RequestNumber // Inner
R => new { R.Year, R.Weeknr, R.Number }, // Outer key
RN => new { RN.Year, RN.Weeknr, RN.Number }, // Inner key
(R, RN) => new
{
R.Year,
RN.Number,
....
}
);
Note because this is EF (or Linq To SQL) you may have to bring down the whole table (use .ToList()) on the Requests and RequestNumber together.
Or better yet if you have access to the database table, simply create a computed column whose heuristics will combine all three columns into a unique identity key. Then in Linq it can join on that id from the computed column.
I have an ASP.NET MVC Application with Entity Framework and MySQL Database. I would like to enforce my code to do all the logic on MySQL server. Obviously I found many cases with similar problem, but I wasn't able to figure out my specific scenario. Thats why I am asking you guys for help.
I have a method like this, it performs a search against space separated words entered by user, each word should be longer than two characters:
protected List<Book> GetBooks(Search search)
{
var db = new ProjectDbContext();
var books = db.Books;
var listTerms = search.SearchTerm.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => s.Length >= 3).ToList().ConvertAll(t => t.ToLower());
var searchedBooks = books//.SqlQuery("", );
.AsEnumerable().Where(book => CheckWhatToSearch(book, search, listTerms));
var sortedBooks = searchedBooks.OrderBy(search.SortBy + " " + search.SortOrder.ToLower()); // dynamic LINQ query helper
var pagedBooks = search.HowMuchSkip >= 0 ?
sortedBooks.Skip(search.HowMuchSkip).Take(search.HowMuchTake) :
Enumerable.Empty<Book>().AsQueryable();
return pagedBooks.ToList();
}
Of course when running this method, I am receiving an error, because EF is unable to convert my custom function to SQL Code
Error: LINQ to Entities does not recognize the method 'Boolean CheckWhatToSearch(MVCDemo.Models.Book, MVCDemo.Models.Search, System.Collections.Generic.List`1[System.String])' method, and this method cannot be translated into a store expression.
CheckWhatToSearch method is defined like this:
private static bool CheckWhatToSearch(Book book, Search search, List<string> listTerms)
{
var db = new ProjectDbContext();
var users = db.Users;
if (book.IsPublic != true)
return false; // skip all not public books
if (listTerms.Count <= 0)
return true; // if user typed nothing, display entire list of books
var sbWhereToSearch = new StringBuilder();
var titleValue = book.Title;
var authorValue = users.Single(u => u.Id == book.AuthorId).UserName;
var categoryValue = book.Category;
var descriptionValue = book.Description;
if (search.IncludeTitle)
sbWhereToSearch.Append(titleValue + " ");
if (search.IncludeAuthor)
sbWhereToSearch.Append(authorValue + " ");
if (search.IncludeCategory)
sbWhereToSearch.Append(categoryValue + " ");
if (search.IncludeDescription)
sbWhereToSearch.Append(descriptionValue + " ");
if (sbWhereToSearch.Length == 0) // default if nothing has been chosen
sbWhereToSearch.Append(titleValue + " ");
return listTerms.All(sbWhereToSearch.ToString().ToLower().Contains); // true if all property values concatenated contain all the words typed by user
}
What exactly do I need to figure out?
How to rewrite code from CheckWhatToSearch method, so I can remove AsEnumerable() and enforce all the logic to be executed on MySQL Server. OR
What SqlQuery could replace the functionality of my CheckWhatToSearch method (in this case I could call it directly)
In second case I started with sth like this:
DROP PROCEDURE IF EXISTS sp_SearchBooks;
CREATE PROCEDURE sp_SearchBooks(
IN p_SearchTerms VARCHAR(1000),
IN p_IncludeTitle TINYINT,
IN p_IncludeAuthor TINYINT,
IN p_IncludeCategory TINYINT,
IN p_IncludeDescription TINYINT)
BEGIN
DECLARE v_fieldsToSearch INT DEFAULT "";
SELECT * FROM tblBooks b
WHERE
LOWER(CONCAT(
CASE p_IncludeTitle WHEN 1 THEN b.Title ELSE "" END,
CASE p_IncludeAuthor WHEN 1 THEN (SELECT u.UserName FROM tblUsers u WHERE u.ID = b.AuthorId) ELSE "" END,
CASE p_IncludeCategory WHEN 1 THEN b.Category ELSE "" END,
CASE p_IncludeDescription WHEN 1 THEN b.Description ELSE "" END))
REGEXP REPLACE(p_SearchTerms, " ", "|");
END;
CALL sp_SearchBooks("word1 word2", 1, 1, 0, 0);
But I don't like my approach and I guess its vulnerable to SQL injection. Besides it matches Any, not All (c# regex is different than MySQL one, there is no (?=...)). (SQL procedure is not finished, I have pasted it to show you my way of thinking, but today I really struggle with MySQL)
I prefer option number 1, with LINQ only.
EDIT (20-12-2015 # 3:30):
Alright, I created stored procedure like this:
DROP PROCEDURE IF EXISTS sp_SearchBooks;
CREATE PROCEDURE sp_SearchBooks(
IN p_SearchTerms VARCHAR(1000),
IN p_IncludeTitle TINYINT,
IN p_IncludeAuthor TINYINT,
IN p_IncludeCategory TINYINT,
IN p_IncludeDescription TINYINT)
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE v_currTerm VARCHAR(100) DEFAULT "";
DROP TEMPORARY TABLE IF EXISTS temp_tblSearchMatches;
CREATE TEMPORARY TABLE temp_tblSearchMatches
(
Id VARCHAR(36),
SearchTerm VARCHAR(100),
CONSTRAINT ck_temp_searchmatches_id CHECK (Id REGEXP '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')
);
WHILE (SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(p_SearchTerms, " end;"), ' ', i), ' ', -1) != "end;") DO
SET v_currTerm = LOWER(SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(p_SearchTerms, " end;"), ' ', i), ' ', -1));
INSERT INTO temp_tblSearchMatches (temp_tblSearchMatches.Id, temp_tblSearchMatches.SearchTerm)
SELECT b.Id, v_currTerm FROM tblBooks b
WHERE
LOWER(CONCAT(
CASE p_IncludeTitle WHEN 1 THEN b.Title ELSE "" END, " ",
CASE p_IncludeAuthor WHEN 1 THEN (SELECT u.UserName FROM tblUsers u WHERE u.ID = b.AuthorId) ELSE "" END, " ",
CASE p_IncludeCategory WHEN 1 THEN b.Category ELSE "" END, " ",
CASE p_IncludeDescription WHEN 1 THEN b.Description ELSE "" END)) LIKE CONCAT("%", v_currTerm, "%");
SET i = i + 1;
END WHILE;
COMMIT;
SELECT b.Id, b.Title, b.Category, b.Description, b.AuthorId, b.Thumbnail, b.AdditionDate, b.IsPublic FROM tblBooks b
WHERE b.Id IN (
SELECT sm.Id
FROM temp_tblSearchMatches sm
GROUP BY sm.Id
HAVING COUNT(sm.SearchTerm) = i - 1);
DROP TEMPORARY TABLE IF EXISTS temp_tblSearchMatches;
END;
Modified method GetBooks
protected List<Book> GetBooks(Search search)
{
var db = new ProjectDbContext();
var books = db.Books;
//var listTerms = search.SearchTerm.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)
// .Where(s => s.Length >= 3).ToList().ConvertAll(t => t.ToLower().Replace("|", ""));
var paramSearchTerms = new MySqlParameter { ParameterName = "p_SearchTerms", Value = search.SearchTerm };
var paramIncludeTitle = new MySqlParameter { ParameterName = "p_IncludeTitle", Value = search.IncludeTitle };
var paramIncludeAuthor = new MySqlParameter { ParameterName = "p_IncludeAuthor", Value = search.IncludeAuthor };
var paramIncludeCategory = new MySqlParameter { ParameterName = "p_IncludeCategory", Value = search.IncludeCategory };
var paramIncludeDescription = new MySqlParameter { ParameterName = "p_IncludeDescription", Value = search.IncludeDescription };
var searchedBooks = books
.SqlQuery("CALL sp_SearchBooks(#p_SearchTerms, #p_IncludeTitle, #p_IncludeAuthor, #p_IncludeCategory, #p_IncludeDescription)", paramSearchTerms, paramIncludeTitle, paramIncludeAuthor, paramIncludeCategory, paramIncludeDescription);
//.AsEnumerable().Where(book => CheckWhatToSearch(book, search, listTerms));
var sortedBooks = searchedBooks.OrderBy(search.SortBy + " " + search.SortOrder.ToLower()); // dynamic LINQ query helper
var pagedBooks = search.HowMuchSkip >= 0 ?
sortedBooks.Skip(search.HowMuchSkip).Take(search.HowMuchTake) :
Enumerable.Empty<Book>().AsQueryable();
return pagedBooks.ToList();
}
But now, I am randomly getting error:
“Context cannot be used while the model is being created”
during materializing in the last line of the method.or deadlock (it never reaches next line). And I am not entirely sure if I was able to mirror CheckWhatToSearch method functionality exactly.
EDIT (24-12-2015)
This is mysql server operation when I am using stored procedure:
151224 0:38:17 44 Init DB project
44 Query CALL sp_SearchBooks('wła', 1, 1, 0, 0)
44 Init DB project
44 Query SELECT
`Extent1`.`Id`,
`Extent1`.`UserName`,
`Extent1`.`Password`,
`Extent1`.`Email`,
`Extent1`.`RegistrationDate`,
`Extent1`.`RetryAttempts`,
`Extent1`.`IsLocked`,
`Extent1`.`LockedDateTime`
FROM `tblUsers` AS `Extent1`
Why, and where the hell it is calling select to retrieve entire users table - I don't know. And I am still getting deadlocks.
Following your suggestion I have tried to implement it using Dynamic Expression but it has proven to be rather difficult, can you guys help me with this?
I have started with code below but I am stuck and I don't know how to properly write concatenation using Expressions. I am missing the point of this I guess cuz I am not sure when during mirroring my method I should use normal variables and methods, and where I should use Expressions (I guess, listterms and search could be left as they are and only things that are book related should be rewritten):
// Parameter of the main predicate
ParameterExpression pe = Expression.Parameter(typeof(Book), "book");
LabelTarget returnTarget = Expression.Label(typeof(bool));
// if (book.IsPublic != true)
// return false;
Expression ifBookNotPublic = Expression.IfThen(
Expression.NotEqual(
Expression.Property(pe, typeof(Book).GetProperty("IsPublic")),
Expression.Constant(true)),
Expression.Return(returnTarget, Expression.Constant(false)));
// if (listTerms.Count <= 0)
// return true;
Expression paramListTerms = Expression.Constant(listTerms);
Expression ifListTermsCountLessOrEqualThanZero = Expression.IfThen(
Expression.LessThanOrEqual(
Expression.Property(paramListTerms, typeof(List<string>).GetProperty("Count")),
Expression.Constant(0, typeof(int))),
Expression.Return(returnTarget, Expression.Constant(true)));
// listTerms.All(s => sbWhereToSearch.ToString().ToLower().Contains(s));
ParameterExpression pTerm = Expression.Parameter(typeof(string), "s");
Expression paramSearch = Expression.Constant(search);
// if (search.IncludeTitle)
// sbWhereToSearch.Append(titleValue + " ");
Expression ifSearchIncludeTitleThenConcat = Expression.IfThen(
Expression.Equal(
Expression.Property(paramSearch, typeof(Search).GetProperty("IncludeTitle")),
Expression.Constant(true)),
Expression.WHAT NOW ? );
// ===================================
var exprBlock = Expression.Block(); // Expression Calls here
var searchedBooks = books.AsQueryable().Where(Expression.Lambda<Func<Book, bool>>(exprBlock, pe)); // book such as whole block returns true for it
I have tried another approach as well, I replaced predicate with anonymous function and it works actually, but for some unknown reason mysql log shows that I am retrieving both tables, despite the fact that Visual Studio shows my data as Queryable and materializes it in the last line only.
Typically you'd want to write that function as a stored procedure, like you started to do. Which should get recognized by EF and allow you to use it as a method on your context.
However, you might be better off writing the GetBooks method as a stored procedure and just invoking that as the request comes in and returning the procedure results. That way the whole thing would get executed on the DB engine instead of on the web server.
The downside of that would be that you'll have a lot of arguments you'll be passing to the stored procedure which might make it a bit messy.
Starting from the point that for EF + is a canonical function and Contains is a canonical function you could just create an expression tree where you have the sum of all the fields you need to check then apply the contains. You can do this inside C# + EF without using stored procedures.
Here you can find an example from Microsoft on how to build an expression tree on IQueryables
https://msdn.microsoft.com/library/bb882637(v=vs.100).aspx
What would be an EF method syntax equivalent for the following TSQL query?
select istb.service_id, ss.service_desc, selected=1
from istb_services istb
inner join setup_services ss on istb.service_id=ss.service_id
where istb.istb_id=3
union
select ss.service_id, ss.service_desc, selected=0
from setup_services ss
where ss.service_id not in (select service_id from istb_services where istb_id=3)
I tried converting the not in part of the query like following:
var _existing = context.istb_services.Where(e => e.istb_id == IstbID);
var _others = context.setup_services.Except(_existing);
but it is generating compile-time error:
The best overloaded method match for 'System.Data.Objects.ObjectQuery.Except(System.Data.Objects.ObjectQuery)' has some invalid arguments
I understand I can't pass different type of ObjectQuery to the .Except method but then what would be the alternative code?
Thanks,
Try the following:
var resultA =
from istb in istb_services
join ss in setup_services on istb.service_id equals ss.service_id
where istb.istb_id == 3
select new { istb.service_id, ss.service_desc, selected = true };
var resultB =
from ss in setup_services
where !istb_services.Any(istb =>
istb.service_id == ss.service_id &&
istb.istb_id == 3)
select new { ss.service_id, ss.service_desc, selected = false };
var result = resultA.Union(resultB);
Anonymous type initializers having identical fields should be compiled to the same anonymous type, making the two sequences compatible for the Union operation.