- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我有一个带有 Entity Framework 和 MySQL 数据库的 ASP.NET MVC 应用程序。我想强制我的代码在 MySQL 服务器上执行所有逻辑。显然我发现了很多有类似问题的案例,但我无法弄清楚我的具体情况。这就是为什么我向你们寻求帮助。
我有一个这样的方法,它对用户输入的空格分隔的单词执行搜索,每个单词应该长于两个字符:
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();
}
当然,运行此方法时,我收到错误,因为 EF 无法将我的自定义函数转换为 SQL 代码
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 方法定义如下:
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
}
我到底需要弄清楚什么?
在第二种情况下,我是这样开始的:
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);
但我不喜欢我的方法,我猜它容易受到 SQL 注入(inject)的攻击。除此之外,它匹配 Any,而不是 All(c# 正则表达式与 MySQL 不同,没有 (?=...))。 (SQL程序没写完,我贴出来是为了给大家展示一下我的思路,不过今天我真的很纠结MySQL)
我更喜欢选项 1,仅使用 LINQ。
编辑(20-12-2015 @ 3:30):
好吧,我创建了这样的存储过程:
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;
修改方法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();
}
但是现在,我随机收到错误:
“Context cannot be used while the model is being created”
在方法的最后一行实现期间。或死锁(它永远不会到达下一行)。而且我不完全确定我是否能够准确反射(reflect) CheckWhatToSearch 方法功能。
编辑(2015 年 12 月 24 日)
这是我使用存储过程时mysql服务器的操作:
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`
为什么,以及它到底在哪里调用 select 来检索整个用户表 - 我不知道。而且我仍然陷入僵局。
按照您的建议,我尝试使用动态表达式来实现它,但事实证明它相当困难,你们能帮我吗?
我已经从下面的代码开始,但我被困住了,我不知道如何使用表达式正确编写串联。我想我错过了这一点,因为我不确定在镜像我的方法期间何时应该使用普通变量和方法,以及应该在哪里使用表达式(我猜,列表术语和搜索可以保留原样,并且只能保留那些与书籍相关的内容应该重写):
// 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
我也尝试了另一种方法,我用匿名函数替换了谓词,它实际上有效,但由于某种未知的原因,mysql日志显示我正在检索两个表,尽管Visual Studio将我的数据显示为可查询并实现仅在最后一行。
最佳答案
通常,您希望将该函数编写为存储过程,就像您开始做的那样。 EF 应该能够识别它,并允许您将其用作上下文中的方法。
但是,您最好将 GetBooks 方法编写为存储过程,然后在请求传入时调用该方法并返回过程结果。这样整个事情就会在数据库引擎上执行,而不是在 Web 服务器上。
这样做的缺点是您将有很多参数传递给存储过程,这可能会使其有点困惑。
关于c# - Entity Framework - 如何将我的特定代码转换为 MySQL 可以理解的表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34376388/
我是一名优秀的程序员,十分优秀!