Переглянути джерело

1. 优化索引结构
2. 优化查询性能
3. 修正删除索引的bug

懒得勤快 1 рік тому
батько
коміт
1f2988e918

+ 0 - 23
Masuit.LuceneEFCore.SearchEngine/Extensions/DocumentExtension.cs

@@ -1,10 +1,8 @@
 using Lucene.Net.Documents;
 using Newtonsoft.Json;
 using System;
-using System.Collections.Generic;
 using System.ComponentModel;
 using System.Globalization;
-using System.Linq;
 
 namespace Masuit.LuceneEFCore.SearchEngine.Extensions
 {
@@ -77,26 +75,5 @@ namespace Masuit.LuceneEFCore.SearchEngine.Extensions
 
 			return Convert.ChangeType(value, type);
 		}
-
-#if NET6_0_OR_GREATER
-#else
-
-		/// <summary>
-		/// 按字段去重
-		/// </summary>
-		/// <typeparam name="TSource"></typeparam>
-		/// <typeparam name="TKey"></typeparam>
-		/// <param name="source"></param>
-		/// <param name="keySelector"></param>
-		/// <returns></returns>
-		public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
-		{
-			if (source == null) throw new ArgumentNullException(nameof(source));
-			if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
-			var set = new HashSet<TKey>();
-			return source.Where(item => set.Add(keySelector(item)));
-		}
-
-#endif
 	}
 }

+ 0 - 14
Masuit.LuceneEFCore.SearchEngine/Extensions/StringHelpers.cs

@@ -43,19 +43,5 @@ namespace Masuit.LuceneEFCore.SearchEngine.Extensions
 				@this.Add(obj);
 			}
 		}
-
-		/// <summary>
-		/// 移除符合条件的元素
-		/// </summary>
-		/// <typeparam name="T"></typeparam>
-		/// <param name="this"></param>
-		/// <param name="where"></param>
-		public static void RemoveWhere<T>(this ICollection<T> @this, Func<T, bool> @where)
-		{
-			foreach (var obj in @this.Where(where).ToList())
-			{
-				@this.Remove(obj);
-			}
-		}
 	}
 }

+ 0 - 7
Masuit.LuceneEFCore.SearchEngine/Interfaces/ILuceneIndexable.cs

@@ -32,13 +32,6 @@ namespace Masuit.LuceneEFCore.SearchEngine.Interfaces
         Guid Id { get; set; }
 #endif
 
-        /// <summary>
-        /// 索引id
-        /// </summary>
-        [LuceneIndex(Name = "IndexId", Store = Field.Store.YES)]
-        [JsonIgnore, NotMapped]
-        internal string IndexId { get; set; }
-
         /// <summary>
         /// 转换成Lucene文档
         /// </summary>

+ 6 - 0
Masuit.LuceneEFCore.SearchEngine/Interfaces/ISearchEngine.cs

@@ -35,6 +35,12 @@ namespace Masuit.LuceneEFCore.SearchEngine.Interfaces
         /// 创建指定数据表的索引
         /// </summary>
         void CreateIndex(List<string> tables);
+        /// <summary>
+        /// 创建数据集索引
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="recreate"></param>
+        void CreateIndex(IEnumerable<ILuceneIndexable> entities, bool recreate = true);
 
         /// <summary>
         /// 删除索引

+ 220 - 218
Masuit.LuceneEFCore.SearchEngine/LuceneIndexSearcher.cs

@@ -19,241 +19,243 @@ using TinyPinyin;
 
 namespace Masuit.LuceneEFCore.SearchEngine
 {
-	public class LuceneIndexSearcher : ILuceneIndexSearcher
-	{
-		private readonly Directory _directory;
-		private readonly Analyzer _analyzer;
-		private readonly IMemoryCache _memoryCache;
+    public class LuceneIndexSearcher : ILuceneIndexSearcher
+    {
+        private readonly Directory _directory;
+        private readonly Analyzer _analyzer;
+        private readonly IMemoryCache _memoryCache;
 
-		/// <summary>
-		/// 构造函数
-		/// </summary>
-		/// <param name="directory">索引目录</param>
-		/// <param name="analyzer">索引分析器</param>
-		/// <param name="memoryCache">内存缓存</param>
-		public LuceneIndexSearcher(Directory directory, Analyzer analyzer, IMemoryCache memoryCache)
-		{
-			_directory = directory;
-			_analyzer = analyzer;
-			_memoryCache = memoryCache;
-		}
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="directory">索引目录</param>
+        /// <param name="analyzer">索引分析器</param>
+        /// <param name="memoryCache">内存缓存</param>
+        public LuceneIndexSearcher(Directory directory, Analyzer analyzer, IMemoryCache memoryCache)
+        {
+            _directory = directory;
+            _analyzer = analyzer;
+            _memoryCache = memoryCache;
+        }
 
-		/// <summary>
-		/// 分词
-		/// </summary>
-		/// <param name="keyword"></param>
-		/// <returns></returns>
-		public List<string> CutKeywords(string keyword)
-		{
-			if (keyword.Length <= 2)
-			{
-				return new List<string>
-				{
-					keyword
-				};
-			}
+        /// <summary>
+        /// 分词
+        /// </summary>
+        /// <param name="keyword"></param>
+        /// <returns></returns>
+        public List<string> CutKeywords(string keyword)
+        {
+            if (keyword.Length <= 2)
+            {
+                return new List<string>
+                {
+                    keyword
+                };
+            }
 
-			keyword = keyword.Replace("AND ", "+").Replace("NOT ", "-").Replace("OR ", " ");
-			return _memoryCache.GetOrCreate(keyword, entry =>
-			{
-				entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
-				var list = new HashSet<string>
-				{
-					keyword
-				};
-				list.AddRange(Regex.Matches(keyword, @""".+""").Cast<Match>().Select(m =>
-				{
-					keyword = keyword.Replace(m.Value, "");
-					return m.Value;
-				}));//必须包含的
-				list.AddRange(Regex.Matches(keyword, @"\s-.+\s?").Cast<Match>().Select(m =>
-				{
-					keyword = keyword.Replace(m.Value, "");
-					return m.Value.Trim();
-				}));//必须不包含的
-				list.AddRange(Regex.Matches(keyword, @"[\u4e00-\u9fa5]+").Cast<Match>().Select(m => m.Value));//中文
-				list.AddRange(Regex.Matches(keyword, @"\p{P}?[A-Z]*[a-z]*[\p{P}|\p{S}]*").Cast<Match>().Select(m => m.Value));//英文单词
-				list.AddRange(Regex.Matches(keyword, "([A-z]+)([0-9.]+)").Cast<Match>().SelectMany(m => m.Groups.Cast<Group>().Select(g => g.Value)));//英文+数字
-				list.AddRange(new JiebaSegmenter().Cut(keyword, true));//结巴分词
-				list.RemoveWhere(s => s.Length < 2);
-				list.AddRange(KeywordsManager.SynonymWords.Where(t => list.Contains(t.key) || list.Contains(t.value)).SelectMany(t => new[] { t.key, t.value }));
-				var pinyins = new HashSet<string>();
-				foreach (var s in list.Select(s => Regex.Replace(s, @"\p{P}|\p{S}", "")).Distinct())
-				{
-					if (!pinyins.Contains(s))
-					{
-						pinyins.AddRange(KeywordsManager.PinyinsLookup[PinyinHelper.GetPinyin(s)]);
-					}
+            keyword = keyword.Replace("AND ", "+").Replace("NOT ", "-").Replace("OR ", " ");
+            return _memoryCache.GetOrCreate(keyword, entry =>
+            {
+                entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
+                var list = new HashSet<string>
+                {
+                    keyword
+                };
+                list.AddRange(Regex.Matches(keyword, @""".+""").Cast<Match>().Select(m =>
+                {
+                    keyword = keyword.Replace(m.Value, "");
+                    return m.Value;
+                }));//必须包含的
+                list.AddRange(Regex.Matches(keyword, @"\s-.+\s?").Cast<Match>().Select(m =>
+                {
+                    keyword = keyword.Replace(m.Value, "");
+                    return m.Value.Trim();
+                }));//必须不包含的
+                list.AddRange(Regex.Matches(keyword, @"[\u4e00-\u9fa5]+").Cast<Match>().Select(m => m.Value));//中文
+                list.AddRange(Regex.Matches(keyword, @"\p{P}?[A-Z]*[a-z]*[\p{P}|\p{S}]*").Cast<Match>().Select(m => m.Value));//英文单词
+                list.AddRange(Regex.Matches(keyword, "([A-z]+)([0-9.]+)").Cast<Match>().SelectMany(m => m.Groups.Cast<Group>().Select(g => g.Value)));//英文+数字
+                list.AddRange(new JiebaSegmenter().Cut(keyword, true));//结巴分词
+                list.RemoveWhere(s => s.Length < 2);
+                list.AddRange(KeywordsManager.SynonymWords.Where(t => list.Contains(t.key) || list.Contains(t.value)).SelectMany(t => new[] { t.key, t.value }));
+                var pinyins = new HashSet<string>();
+                foreach (var s in list.Select(s => Regex.Replace(s, @"\p{P}|\p{S}", "")).Distinct())
+                {
+                    if (!pinyins.Contains(s))
+                    {
+                        pinyins.AddRange(KeywordsManager.PinyinsLookup[PinyinHelper.GetPinyin(s)]);
+                    }
 
-					var lower = s.ToLower();
-					if (KeywordsManager.PinyinsLookup.Contains(lower))
-					{
-						pinyins.AddRange(KeywordsManager.PinyinsLookup[lower]);
-					}
-				}
+                    var lower = s.ToLower();
+                    if (KeywordsManager.PinyinsLookup.Contains(lower))
+                    {
+                        pinyins.AddRange(KeywordsManager.PinyinsLookup[lower]);
+                    }
+                }
 
-				return list.Union(pinyins).OrderByDescending(s => s.Length).Take(10).Select(s => s.Trim('[', ']', '{', '}', '(', ')')).ToList();
-			});
-		}
+                return list.Union(pinyins).OrderByDescending(s => s.Length).Take(10).Select(s => s.Trim('[', ']', '{', '}', '(', ')')).ToList();
+            });
+        }
 
-		/// <summary>
-		/// 分词模糊查询
-		/// </summary>
-		/// <param name="parser">条件</param>
-		/// <param name="keywords">关键词</param>
-		/// <returns></returns>
-		private BooleanQuery GetFuzzyquery(MultiFieldQueryParser parser, string keywords)
-		{
-			var finalQuery = new BooleanQuery();
-			var terms = CutKeywords(keywords);
-			foreach (var term in terms)
-			{
-				try
-				{
-					if (term.StartsWith("\""))
-					{
-						finalQuery.Add(parser.Parse(term.Trim('"')), Occur.MUST);
-					}
-					else if (term.StartsWith("-"))
-					{
-						finalQuery.Add(parser.Parse(term), Occur.MUST_NOT);
-					}
-					else
-					{
-						finalQuery.Add(parser.Parse(term.Replace("~", "") + "~"), Occur.SHOULD);
-					}
-				}
-				catch (ParseException)
-				{
-					finalQuery.Add(parser.Parse(Regex.Replace(term, @"\p{P}|\p{S}", "")), Occur.SHOULD);
-				}
-			}
+        /// <summary>
+        /// 分词模糊查询
+        /// </summary>
+        /// <param name="parser">条件</param>
+        /// <param name="keywords">关键词</param>
+        /// <returns></returns>
+        private BooleanQuery GetFuzzyquery(MultiFieldQueryParser parser, string keywords)
+        {
+            var finalQuery = new BooleanQuery();
+            var terms = CutKeywords(keywords);
+            foreach (var term in terms)
+            {
+                try
+                {
+                    if (term.StartsWith("\""))
+                    {
+                        finalQuery.Add(parser.Parse(term.Trim('"')), Occur.MUST);
+                    }
+                    else if (term.StartsWith("-"))
+                    {
+                        finalQuery.Add(parser.Parse(term), Occur.MUST_NOT);
+                    }
+                    else
+                    {
+                        finalQuery.Add(parser.Parse(term.Replace("~", "") + "~"), Occur.SHOULD);
+                    }
+                }
+                catch (ParseException)
+                {
+                    finalQuery.Add(parser.Parse(Regex.Replace(term, @"\p{P}|\p{S}", "")), Occur.SHOULD);
+                }
+            }
 
-			return finalQuery;
-		}
+            return finalQuery;
+        }
 
-		/// <summary>
-		/// 执行搜索
-		/// </summary>
-		/// <param name="options">搜索选项</param>
-		/// <param name="safeSearch">启用安全搜索</param>
-		/// <returns></returns>
-		private ILuceneSearchResultCollection PerformSearch(SearchOptions options, bool safeSearch)
-		{
-			// 结果集
-			ILuceneSearchResultCollection results = new LuceneSearchResultCollection();
-			using var reader = DirectoryReader.Open(_directory);
-			var searcher = new IndexSearcher(reader);
-			Query query;
+        /// <summary>
+        /// 执行搜索
+        /// </summary>
+        /// <param name="options">搜索选项</param>
+        /// <param name="safeSearch">启用安全搜索</param>
+        /// <returns></returns>
+        private ILuceneSearchResultCollection PerformSearch(SearchOptions options, bool safeSearch)
+        {
+            // 结果集
+            ILuceneSearchResultCollection results = new LuceneSearchResultCollection();
+            using var reader = DirectoryReader.Open(_directory);
+            var searcher = new IndexSearcher(reader);
+            Query query;
 
-			// 启用安全搜索
-			if (safeSearch)
-			{
-				options.Keywords = QueryParserBase.Escape(options.Keywords);
-			}
+            // 启用安全搜索
+            if (safeSearch)
+            {
+                options.Keywords = QueryParserBase.Escape(options.Keywords);
+            }
 
-			if (options.Fields.Count == 1)
-			{
-				// 单字段搜索
-				var queryParser = new QueryParser(Lucene.Net.Util.LuceneVersion.LUCENE_48, options.Fields[0], _analyzer);
-				query = queryParser.Parse(options.Keywords);
-			}
-			else
-			{
-				// 多字段搜索
-				var queryParser = new MultiFieldQueryParser(Lucene.Net.Util.LuceneVersion.LUCENE_48, options.Fields.ToArray(), _analyzer, options.Boosts);
-				query = GetFuzzyquery(queryParser, options.Keywords);
-			}
+            if (options.Fields.Count == 1)
+            {
+                // 单字段搜索
+                var queryParser = new QueryParser(Lucene.Net.Util.LuceneVersion.LUCENE_48, options.Fields[0], _analyzer);
+                query = queryParser.Parse(options.Keywords);
+            }
+            else
+            {
+                // 多字段搜索
+                var queryParser = new MultiFieldQueryParser(Lucene.Net.Util.LuceneVersion.LUCENE_48, options.Fields.ToArray(), _analyzer, options.Boosts);
+                query = GetFuzzyquery(queryParser, options.Keywords);
+            }
 
-			// 排序规则处理
-			var sort = new Sort(options.OrderBy.ToArray());
-			Expression<Func<ScoreDoc, bool>> where = m => m.Score >= options.Score;
-			if (options.Type != null)
-			{
-				// 过滤掉已经设置了类型的对象
-				where = where.And(m => options.Type.AssemblyQualifiedName == searcher.Doc(m.Doc).Get("Type"));
-			}
+            // 排序规则处理
+            var sort = new Sort(options.OrderBy.ToArray());
+            if (options.Type != null)
+            {
+                query=new BooleanQuery()
+                {
+                    {query,Occur.MUST},
+                    {new TermQuery(new Term("Type",options.Type.AssemblyQualifiedName)),Occur.MUST}
+                };
+            }
+            
+            var matches = searcher.Search(query, options.Filter, options.MaximumNumberOfHits, sort, true, true).ScoreDocs.Where(m => m.Score >= options.Score);
+            results.TotalHits = matches.Count();
 
-			var matches = searcher.Search(query, options.Filter, options.MaximumNumberOfHits, sort, true, true).ScoreDocs.Where(where.Compile()).DistinctBy(m => searcher.Doc(m.Doc).Get("IndexId"));
-			results.TotalHits = matches.Count();
+            // 分页处理
+            if (options.Skip.HasValue)
+            {
+                matches = matches.Skip(options.Skip.Value);
+            }
+            if (options.Take.HasValue)
+            {
+                matches = matches.Take(options.Take.Value);
+            }
 
-			// 分页处理
-			if (options.Skip.HasValue)
-			{
-				matches = matches.Skip(options.Skip.Value);
-			}
-			if (options.Take.HasValue)
-			{
-				matches = matches.Take(options.Take.Value);
-			}
+            var docs = matches.ToList();
 
-			var docs = matches.ToList();
+            // 创建结果集
+            foreach (var match in docs)
+            {
+                var doc = searcher.Doc(match.Doc);
+                results.Results.Add(new LuceneSearchResult()
+                {
+                    Score = match.Score,
+                    Document = doc
+                });
+            }
 
-			// 创建结果集
-			foreach (var match in docs)
-			{
-				var doc = searcher.Doc(match.Doc);
-				results.Results.Add(new LuceneSearchResult()
-				{
-					Score = match.Score,
-					Document = doc
-				});
-			}
+            return results;
+        }
 
-			return results;
-		}
+        /// <summary>
+        /// 搜索单条记录
+        /// </summary>
+        /// <param name="options"></param>
+        /// <returns></returns>
+        public Document ScoredSearchSingle(SearchOptions options)
+        {
+            options.MaximumNumberOfHits = 1;
+            var results = ScoredSearch(options);
+            return results.TotalHits > 0 ? results.Results.First().Document : null;
+        }
 
-		/// <summary>
-		/// 搜索单条记录
-		/// </summary>
-		/// <param name="options"></param>
-		/// <returns></returns>
-		public Document ScoredSearchSingle(SearchOptions options)
-		{
-			options.MaximumNumberOfHits = 1;
-			var results = ScoredSearch(options);
-			return results.TotalHits > 0 ? results.Results.First().Document : null;
-		}
+        /// <summary>
+        /// 按权重搜索
+        /// </summary>
+        /// <param name="options">搜索选项</param>
+        /// <returns></returns>
+        public ILuceneSearchResultCollection ScoredSearch(SearchOptions options)
+        {
+            ILuceneSearchResultCollection results;
+            var sw = Stopwatch.StartNew();
+            try
+            {
+                results = PerformSearch(options, false);
+            }
+            catch (ParseException)
+            {
+                results = PerformSearch(options, true);
+            }
 
-		/// <summary>
-		/// 按权重搜索
-		/// </summary>
-		/// <param name="options">搜索选项</param>
-		/// <returns></returns>
-		public ILuceneSearchResultCollection ScoredSearch(SearchOptions options)
-		{
-			ILuceneSearchResultCollection results;
-			var sw = Stopwatch.StartNew();
-			try
-			{
-				results = PerformSearch(options, false);
-			}
-			catch (ParseException)
-			{
-				results = PerformSearch(options, true);
-			}
+            sw.Stop();
+            results.Elapsed = sw.ElapsedMilliseconds;
+            return results;
+        }
 
-			sw.Stop();
-			results.Elapsed = sw.ElapsedMilliseconds;
-			return results;
-		}
-
-		/// <summary>
-		/// 按权重搜索
-		/// </summary>
-		/// <param name="keywords">关键词</param>
-		/// <param name="fields">限定检索字段</param>
-		/// <param name="maximumNumberOfHits">最大检索量</param>
-		/// <param name="boosts">多字段搜索时,给字段的搜索加速</param>
-		/// <param name="type">文档类型</param>
-		/// <param name="sortBy">排序规则</param>
-		/// <param name="skip">跳过多少条</param>
-		/// <param name="take">取多少条</param>
-		/// <returns></returns>
-		public ILuceneSearchResultCollection ScoredSearch(string keywords, string fields, int maximumNumberOfHits, Dictionary<string, float> boosts, Type type, string sortBy, int? skip, int? take)
-		{
-			var options = new SearchOptions(keywords, fields, maximumNumberOfHits, boosts, type, sortBy, skip, take);
-			return ScoredSearch(options);
-		}
-	}
+        /// <summary>
+        /// 按权重搜索
+        /// </summary>
+        /// <param name="keywords">关键词</param>
+        /// <param name="fields">限定检索字段</param>
+        /// <param name="maximumNumberOfHits">最大检索量</param>
+        /// <param name="boosts">多字段搜索时,给字段的搜索加速</param>
+        /// <param name="type">文档类型</param>
+        /// <param name="sortBy">排序规则</param>
+        /// <param name="skip">跳过多少条</param>
+        /// <param name="take">取多少条</param>
+        /// <returns></returns>
+        public ILuceneSearchResultCollection ScoredSearch(string keywords, string fields, int maximumNumberOfHits, Dictionary<string, float> boosts, Type type, string sortBy, int? skip, int? take)
+        {
+            var options = new SearchOptions(keywords, fields, maximumNumberOfHits, boosts, type, sortBy, skip, take);
+            return ScoredSearch(options);
+        }
+    }
 }

+ 7 - 28
Masuit.LuceneEFCore.SearchEngine/LuceneIndexableBaseEntity.cs

@@ -5,6 +5,7 @@ using Newtonsoft.Json;
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
 using System.Reflection;
 
 namespace Masuit.LuceneEFCore.SearchEngine
@@ -34,20 +35,6 @@ namespace Masuit.LuceneEFCore.SearchEngine
         public Guid Id { get; set; }
 #endif
 
-        /// <summary>
-        /// 索引唯一id
-        /// </summary>
-        [LuceneIndex(Name = nameof(ILuceneIndexable.IndexId), Store = Field.Store.YES)]
-        [NotMapped, JsonIgnore]
-        string ILuceneIndexable.IndexId
-        {
-            get => LuceneIndexerOptions.IndexIdGenerator(GetType(), Id);
-
-            set
-            {
-            }
-        }
-
         /// <summary>
         /// 转换成Lucene文档
         /// </summary>
@@ -63,29 +50,21 @@ namespace Masuit.LuceneEFCore.SearchEngine
 
             var classProperties = type.GetProperties();
             doc.Add(new StringField("Type", type.AssemblyQualifiedName, Field.Store.YES));
+            doc.Add(new StringField("IndexId", type.FullName + Id, Field.Store.YES));
             foreach (var propertyInfo in classProperties)
             {
-                var propertyValue = propertyInfo.GetValue(this);
-                if (propertyValue == null)
+                var attrs = propertyInfo.GetCustomAttributes<LuceneIndexAttribute>().ToList();
+                if (attrs.Count==0)
                 {
                     continue;
                 }
 
-                //1. 该处修复用IndexId去删除索引无效的问题
-                //2. 以Id为目标的删除放在其他处: 也利用到了IndexId
-                if (propertyInfo.Name == nameof(ILuceneIndexable.IndexId))
+                var propertyValue = propertyInfo.GetValue(this);
+                if (propertyValue == null)
                 {
-                    var filed = new Field(propertyInfo.Name, propertyValue.ToString(), new FieldType
-                    {
-                        IsStored = true,
-                        IsIndexed = true,
-                        IsTokenized = false
-                    });
-                    doc.Add(filed);
                     continue;
                 }
 
-                var attrs = propertyInfo.GetCustomAttributes<LuceneIndexAttribute>();
                 foreach (var attr in attrs)
                 {
                     string name = !string.IsNullOrEmpty(attr.Name) ? attr.Name : propertyInfo.Name;
@@ -126,4 +105,4 @@ namespace Masuit.LuceneEFCore.SearchEngine
             return doc;
         }
     }
-}
+}

+ 11 - 6
Masuit.LuceneEFCore.SearchEngine/LuceneIndexer.cs

@@ -104,6 +104,7 @@ namespace Masuit.LuceneEFCore.SearchEngine
                 {
                     writer.Commit();
                 }
+
                 writer.Flush(true, true);
             }
             catch (Exception ex)
@@ -141,23 +142,27 @@ namespace Masuit.LuceneEFCore.SearchEngine
             using var writer = new IndexWriter(_directory, config);
             foreach (var change in changeset.Entries)
             {
+                var type = change.Entity.GetType();
+                if (type.Assembly.IsDynamic && type.FullName.Contains("Prox"))
+                {
+                    type = type.BaseType;
+                }
+
                 switch (change.State)
                 {
                     case LuceneIndexState.Removed:
-                        //writer.DeleteDocuments(new Term("Id", change.Entity.Id.ToString()));
-                        writer.DeleteDocuments(new Term("IndexId", change.Entity.IndexId));
+                        writer.DeleteDocuments(new Term("IndexId", type.FullName + change.Entity.Id));
                         break;
 
                     case LuceneIndexState.Added:
                     case LuceneIndexState.Updated:
-                        //writer.DeleteDocuments(new Term("Id", change.Entity.Id.ToString()));
-                        writer.DeleteDocuments(new Term("IndexId", change.Entity.IndexId));
+                        writer.DeleteDocuments(new Term("IndexId", type.FullName + change.Entity.Id));
                         writer.AddDocument(change.Entity.ToDocument());
                         break;
                 }
             }
 
-            writer.Flush(true, changeset.HasDeletes);
+            writer.Flush(true, true);
             writer.Commit();
         }
 
@@ -180,4 +185,4 @@ namespace Masuit.LuceneEFCore.SearchEngine
             }
         }
     }
-}
+}

+ 1 - 9
Masuit.LuceneEFCore.SearchEngine/LuceneIndexerOptions.cs

@@ -1,6 +1,4 @@
-using System;
-
-namespace Masuit.LuceneEFCore.SearchEngine
+namespace Masuit.LuceneEFCore.SearchEngine
 {
     /// <summary>
     /// 索引器选项
@@ -11,11 +9,5 @@ namespace Masuit.LuceneEFCore.SearchEngine
         /// 索引路径
         /// </summary>
         public string Path { get; set; }
-
-        /// <summary>
-        /// 索引列IndexId的生成函数,(Type EntityType, any IdValue) => string IndexId
-        /// </summary>
-        public static Func<Type, object, string> IndexIdGenerator = (type, id) => $"{type.Name}:{id}";
-
     }
 }

+ 1 - 1
Masuit.LuceneEFCore.SearchEngine/Masuit.LuceneEFCore.SearchEngine.csproj

@@ -9,7 +9,7 @@
         <Copyright>懒得勤快</Copyright>
         <PackageProjectUrl>https://github.com/ldqk/Masuit.LuceneEFCore.SearchEngine</PackageProjectUrl>
         <PackageId>Masuit.LuceneEFCore.SearchEngine_int</PackageId>
-        <Version>24.1</Version>
+        <Version>24.2</Version>
         <Configurations>Debug;Release;String版本;Guid版本;Long版本</Configurations>
         <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
         <RunAnalyzersDuringLiveAnalysis>false</RunAnalyzersDuringLiveAnalysis>

+ 23 - 4
Masuit.LuceneEFCore.SearchEngine/SearchEngine.cs

@@ -108,6 +108,7 @@ namespace Masuit.LuceneEFCore.SearchEngine
             {
                 p.SetValue(obj, doc.Get(p.Name, p.PropertyType));
             }
+
             return obj;
         }
 
@@ -172,7 +173,12 @@ namespace Masuit.LuceneEFCore.SearchEngine
                 if (typeof(IQueryable<ILuceneIndexable>).IsAssignableFrom(pi.PropertyType))
                 {
                     var entities = Context.GetType().GetProperty(pi.Name).GetValue(Context, null) as IQueryable<ILuceneIndexable>;
-                    LuceneIndexer.CreateIndex(entities, false);
+                    var count = entities.Count();
+                    var pages = count * 1m / 100;
+                    for (int i = 0; i < pages; i++)
+                    {
+                        LuceneIndexer.CreateIndex(entities.Skip(i * 100).Take(100).ToList(), false);
+                    }
                 }
             }
         }
@@ -193,11 +199,24 @@ namespace Masuit.LuceneEFCore.SearchEngine
                 if (typeof(IQueryable<ILuceneIndexable>).IsAssignableFrom(pi.PropertyType) && tables.Contains(pi.Name))
                 {
                     var entities = Context.GetType().GetProperty(pi.Name).GetValue(Context, null) as IQueryable<ILuceneIndexable>;
-                    LuceneIndexer.CreateIndex(entities, false);
+                    var count = entities.Count();
+                    var pages = count * 1m / 100;
+                    for (int i = 0; i < pages; i++)
+                    {
+                        LuceneIndexer.CreateIndex(entities.Skip(i * 100).Take(100).ToList(), false);
+                    }
                 }
             }
         }
 
+        /// <summary>
+        /// 创建数据集索引
+        /// </summary>
+        public void CreateIndex(IEnumerable<ILuceneIndexable> entities, bool recreate = true)
+        {
+            LuceneIndexer.CreateIndex(entities,recreate);
+        }
+
         /// <summary>
         /// 删除索引
         /// </summary>
@@ -224,7 +243,7 @@ namespace Masuit.LuceneEFCore.SearchEngine
             var sw = Stopwatch.StartNew();
             foreach (var indexResult in indexResults.Results)
             {
-                var entity = (T)GetConcreteFromDocument(indexResult.Document);
+                var entity = (T) GetConcreteFromDocument(indexResult.Document);
                 resultSet.Results.Add(entity);
             }
 
@@ -255,7 +274,7 @@ namespace Masuit.LuceneEFCore.SearchEngine
             {
                 IScoredSearchResult<T> result = new ScoredSearchResult<T>();
                 result.Score = indexResult.Score;
-                result.Entity = (T)GetConcreteFromDocument(indexResult.Document);
+                result.Entity = (T) GetConcreteFromDocument(indexResult.Document);
                 results.Results.Add(result);
             }
 

+ 3 - 3
WebSearchDemo/WebSearchDemo.csproj

@@ -10,9 +10,9 @@
     <DocumentationFile>D:\Private\Masuit.LuceneEFCore.SearchEngine\WebSearchDemo\WebSearchDemo.xml</DocumentationFile>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.4" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.4" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.1" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Masuit.LuceneEFCore.SearchEngine\Masuit.LuceneEFCore.SearchEngine.csproj" />