gpt4 book ai didi

c# - Entity Framework - 如何将我的特定代码转换为 MySQL 可以理解的表达式

转载 作者:行者123 更新时间:2023-11-29 21:42:37 26 4
gpt4 key购买 nike

我有一个带有 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
}

我到底需要弄清楚什么?

  1. 如何重写 CheckWhatToSearch 方法中的代码,以便我可以删除 AsEnumerable() 并强制在 MySQL 服务器上执行所有逻辑。或
  2. What SqlQuery 可以替代我的 CheckWhatToSearch 方法的功能(在本例中我可以直接调用它)

在第二种情况下,我是这样开始的:

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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com