entity framework,代码优先和全文search
我意识到很多问题都被问及全文search和entity framework,但我希望这个问题有点不同。
我使用的是entity framework,Code First,需要进行全文search。 当我需要执行全文search时,我通常也会有其他标准/限制 – 比如跳过前500行,或者在另一列上过滤等等。
我看到这已经使用表值函数处理 – 请参阅http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL—Enabling-Fulltext-searching.aspx 。 这似乎是正确的想法。
不幸的是,直到entity framework5.0(即使那样,我相信它们不被Code First支持),表值函数也不被支持。
我真正的问题是什么build议最好的方式来处理这个,entity framework4.3和entity framework5.0。 但具体来说:
-
除了dynamicSQL(例如通过
System.Data.Entity.DbSet.SqlQuery
),是否有任何选项可用于Entity Framework 4.3? -
如果我升级到entity framework5.0,有没有办法,我可以使用表值函数与代码第一?
谢谢,埃里克
使用EF6中引入的拦截器,可以在linq中标记全文search,然后按照http://www.entityframework.info/Home/FullTextSearch中的描述将其replace为dbcommand:;
public class FtsInterceptor : IDbCommandInterceptor { private const string FullTextPrefix = "-FTSPREFIX-"; public static string Fts(string search) { return string.Format("({0}{1})", FullTextPrefix, search); } public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { RewriteFullTextQuery(command); } public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { } public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { RewriteFullTextQuery(command); } public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { } public static void RewriteFullTextQuery(DbCommand cmd) { string text = cmd.CommandText; for (int i = 0; i < cmd.Parameters.Count; i++) { DbParameter parameter = cmd.Parameters[i]; if (parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength)) { if (parameter.Value == DBNull.Value) continue; var value = (string)parameter.Value; if (value.IndexOf(FullTextPrefix) >= 0) { parameter.Size = 4096; parameter.DbType = DbType.AnsiStringFixedLength; value = value.Replace(FullTextPrefix, ""); // remove prefix we added n linq query value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE parameter.Value = value; cmd.CommandText = Regex.Replace(text, string.Format( @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName), string.Format(@"contains([$1].[$2], @{0})", parameter.ParameterName)); if (text == cmd.CommandText) throw new Exception("FTS was not replaced on: " + text); text = cmd.CommandText; } } } } } static class LanguageExtensions { public static bool In<T>(this T source, params T[] list) { return (list as IList<T>).Contains(source); } }
例如,如果您使用FTS-indexed字段NoteText注释了类:
public class Note { public int NoteId { get; set; } public string NoteText { get; set; } }
和EF地图
public class NoteMap : EntityTypeConfiguration<Note> { public NoteMap() { // Primary Key HasKey(t => t.NoteId); } }
和上下文:
public class MyContext : DbContext { static MyContext() { DbInterception.Add(new FtsInterceptor()); } public MyContext(string nameOrConnectionString) : base(nameOrConnectionString) { } public DbSet<Note> Notes { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new NoteMap()); } }
你可以有非常简单的语法来FTS查询:
class Program { static void Main(string[] args) { var s = FtsInterceptor.Fts("john"); using (var db = new MyContext("CONNSTRING")) { var q = db.Notes.Where(n => n.NoteText.Contains(s)); var result = q.Take(10).ToList(); } } }
这将生成像SQL一样
exec sp_executesql N'SELECT TOP (10) [Extent1].[NoteId] AS [NoteId], [Extent1].[NoteText] AS [NoteText] FROM [NS].[NOTES] AS [Extent1] WHERE contains([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 char(4096)',@p__linq__0='(john)
请注意你应该使用局部variables,不能像expression式那样移动FTS包装器
var q = db.Notes.Where(n => n.NoteText.Contains(FtsInterceptor.Fts("john")));
我发现实现这个最简单的方法是在SQL Server中设置和configuration全文search,然后使用存储过程。 将你的parameter passing给SQL,允许DB完成它的工作,返回一个复杂的对象或将结果映射到一个实体。 你不一定要有dynamicSQL,但它可能是最佳的。 例如,如果您需要分页,则可以在每个请求中传递PageNumber和PageSize ,而不需要dynamicSQL。 但是,如果每个查询的参数数量波动,这将是最佳的解决scheme。
我最近有一个类似的要求,最后写了一个专门为微软全文索引访问的IQueryable扩展,它在这里可用IQueryableFreeTextExtensions
正如其他人提到的,我会说开始使用Lucene.NET
Lucene的学习曲线非常高,但是我发现了一个名为“ SimpleLucene ”的封装,可以在CodePlex上find
让我引用博客中的几个代码块来向您展示使用它是多么容易。 我刚刚开始使用它,但得到了它的速度非常快。
首先,从你的仓库获取一些实体,或者在你的情况下,使用entity framework
public class Repository { public IList<Product> Products { get { return new List<Product> { new Product { Id = 1, Name = "Football" }, new Product { Id = 2, Name = "Coffee Cup"}, new Product { Id = 3, Name = "Nike Trainers"}, new Product { Id = 4, Name = "Apple iPod Nano"}, new Product { Id = 5, Name = "Asus eeePC"}, }; } } }
接下来你要做的是创build一个索引定义
public class ProductIndexDefinition : IIndexDefinition<Product> { public Document Convert(Product p) { var document = new Document(); document.Add(new Field("id", p.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); document.Add(new Field("name", p.Name, Field.Store.YES, Field.Index.ANALYZED)); return document; } public Term GetIndex(Product p) { return new Term("id", p.Id.ToString()); } }
并为其创build一个search索引。
var writer = new DirectoryIndexWriter( new DirectoryInfo(@"c:\index"), true); var service = new IndexService(); service.IndexEntities(writer, Repository().Products, ProductIndexDefinition());
所以,你现在有一个可search的索引。 唯一剩下要做的就是..,search! 你可以做很棒的事情,但是它可以像这样简单:(更多的例子参见blog或者codeplex的文档)
var searcher = new DirectoryIndexSearcher( new DirectoryInfo(@"c:\index"), true); var query = new TermQuery(new Term("name", "Football")); var searchService = new SearchService(); Func<Document, ProductSearchResult> converter = (doc) => { return new ProductSearchResult { Id = int.Parse(doc.GetValues("id")[0]), Name = doc.GetValues("name")[0] }; }; IList<Product> results = searchService.SearchIndex(searcher, query, converter);
这里的例子http://www.entityframework.info/Home/FullTextSearch是不完整的解决scheme。; 您需要了解全文search的工作方式。 想象一下,你有一个search字段,用户input2个单词来search。 上面的代码会抛出一个exception。 您需要先对search短语进行预处理,然后使用逻辑“与”或“或”来将其传递给查询。
例如你的search短语是“blah blah2”,那么你需要将其转换为:
var searchTerm = @"\"blah\" AND/OR \"blah2\" ";
完整的解决scheme将是:
value = Regex.Replace(value, @"\s+", " "); //replace multiplespaces value = Regex.Replace(value, @"[^a-zA-Z0-9 -]", "").Trim();//remove non-alphanumeric characters and trim spaces if (value.Any(Char.IsWhiteSpace)) { value = PreProcessSearchKey(value); } public static string PreProcessSearchKey(string searchKey) { var splitedKeyWords = searchKey.Split(null); //split from whitespaces // string[] addDoubleQuotes = new string[splitedKeyWords.Length]; for (int j = 0; j < splitedKeyWords.Length; j++) { splitedKeyWords[j] = $"\"{splitedKeyWords[j]}\""; } return string.Join(" AND ", splitedKeyWords); }
这个方法使用AND逻辑运算符。 您可能会将其作为parameter passing给AND和OR运算符。
您必须转义非字母数字字符,否则当用户input字母数字字符并且没有适当的服务器站点模型级别validation时,它将引发exception。
- EF模式首先或代码优先的方法?
- 如何使用entity framework来声明一对一的关系4 Code First(POCO)
- 枚举与EF代码优先 – 标准方法播种数据库,然后使用?
- EntityType“IdentityUserLogin”没有定义密钥。 定义这个EntityType的关键
- 在生产中使用entity framework(代码优先)迁移
- WithOptionalDependent vs WithOptionalPrinciple – Definitive Answer?
- MapKey与HasForeignKey差异 – Fluent Api
- 代码优先与模型/数据库优先
- 如何指定一个属性应该生成一个TEXT列而不是nvarchar(4000)