diff --git a/src/FreeRedis/Internal/RespHelper.cs b/src/FreeRedis/Internal/RespHelper.cs index 181507a..4b72b27 100644 --- a/src/FreeRedis/Internal/RespHelper.cs +++ b/src/FreeRedis/Internal/RespHelper.cs @@ -617,6 +617,9 @@ internal static string DisplayCsharp(this Type type, bool isNameSpace = true) if (genericParameters.Any() == false) return sb.Append(type.Name).ToString(); + var idxof = type.Name.IndexOf('`'); + if (idxof == -1) return sb.Append(type.Name).ToString(); + sb.Append(type.Name.Remove(type.Name.IndexOf('`'))).Append("<"); var genericTypeIndex = 0; foreach (var genericType in genericParameters) diff --git a/src/FreeRedis/RedisClient/Modules/RediSearch/FtDocumentRepository.cs b/src/FreeRedis/RedisClient/Modules/RediSearch/FtDocumentRepository.cs index 9a631d0..511caf5 100644 --- a/src/FreeRedis/RedisClient/Modules/RediSearch/FtDocumentRepository.cs +++ b/src/FreeRedis/RedisClient/Modules/RediSearch/FtDocumentRepository.cs @@ -139,11 +139,26 @@ public void CreateIndex() void Save(T doc, RedisClient.PipelineHook pipe) { var key = $"{_schema.DocumentAttribute.Prefix}{_schema.KeyProperty.GetValue(doc, null)}"; - var opts = _schema.Fields.Where((a, b) => b > 0).Select((a, b) => new object[] { a.FieldAttribute.FieldName, a.Property.GetValue(doc, null) }).SelectMany(a => a).ToArray(); + var opts = _schema.Fields.Where((a, b) => b > 0).Select((a, b) => new object[] + { + a.FieldAttribute.FieldName, + toRedisValue(a) + }).SelectMany(a => a).ToArray(); var field = _schema.Fields[0].FieldAttribute.FieldName; var value = _schema.Fields[0].Property.GetValue(doc, null); if (pipe != null) pipe.HMSet(key, field, value, opts); else _client.HMSet(key, field, value, opts); + + object toRedisValue(DocumentSchemaFieldInfo dsf) + { + var val = dsf.Property.GetValue(doc, null); + if (dsf.FieldType == FieldType.Tag) + { + if (dsf.Property.PropertyType.IsArrayOrList()) + val = string.Join((dsf.FieldAttribute as FtTagFieldAttribute).Separator ?? ",", typeof(string[]).FromObject(val) as string[]); + } + return val; + } } public void Save(T doc) => Save(doc, null); public void Save(T[] docs) => Save(docs as IEnumerable); @@ -237,6 +252,14 @@ object toFtObject(object param) return string.Concat("\"", param.ToString().Replace("\\", "\\\\").Replace("\"", "\\\""), "\""); } + string toFtTagString(string expResultStr) + { + if (expResultStr == null) return ""; + if (expResultStr.StartsWith("\"") && expResultStr.EndsWith("\"")) + return expResultStr.Substring(1, expResultStr.Length - 2) + .Replace("\\\"", "\"").Replace("\\\\", "\\"); + return expResultStr; + } if (exp == null) return ""; switch (exp.NodeType) @@ -341,6 +364,7 @@ string ParseMemberAccessDateTime() case "System.String": callParseResult = ParseCallString(); break; case "System.Math": callParseResult = ParseCallMath(); break; case "System.DateTime": callParseResult = ParseCallDateTime(); break; + default: callParseResult = ParseCallOther(); break; } if (!string.IsNullOrEmpty(callParseResult)) return callParseResult; break; @@ -439,6 +463,44 @@ string ParseCallDateTime() } return null; } + string ParseCallOther() + { + var objExp = callExp.Object; + var objType = objExp?.Type; + if (objType?.FullName == "System.Byte[]") return null; + + var argIndex = 0; + if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable)) + { + objExp = callExp.Arguments.FirstOrDefault(); + objType = objExp?.Type; + argIndex++; + + if (objType == typeof(string)) + { + switch (callExp.Method.Name) + { + case "First": + case "FirstOrDefault": + return $"substr({parseExp(callExp.Arguments[0])},0,1)"; + } + } + } + if (objType == null) objType = callExp.Method.DeclaringType; + if (objType != null || objType.IsArrayOrList()) + { + string left = null; + switch (callExp.Method.Name) + { + case "Contains": + + left = objExp == null ? null : parseExp(objExp); + var args1 = parseExp(callExp.Arguments[argIndex]); + return $"{left}:{{{toFtTagString(args1)}}}"; + } + } + return null; + } } if (exp is BinaryExpression expBinary && expBinary != null) { @@ -467,7 +529,7 @@ string ParseCallDateTime() if (field.FieldType == FieldType.Text) return $"{(expBinary.NodeType == ExpressionType.NotEqual ? "-" : "")}{parseExp(expBinary.Left)}:{equalRight}"; else if (field.FieldType == FieldType.Tag) - return $"{(expBinary.NodeType == ExpressionType.NotEqual ? "-" : "")}{parseExp(expBinary.Left)}:{{{equalRight}}}"; + return $"{(expBinary.NodeType == ExpressionType.NotEqual ? "-" : "")}{parseExp(expBinary.Left)}:{{{toFtTagString(equalRight)}}}"; } return $"{(expBinary.NodeType == ExpressionType.NotEqual ? "-" : "")}{parseExp(expBinary.Left)}:[{equalRight} {equalRight}]"; case ExpressionType.Add: @@ -586,8 +648,21 @@ public List ToList(out long total) var keyProperty = _repository._schema.KeyProperty; var result = _searchBuilder.Execute(); total = result.Total; - return result.Documents.Select(doc => { - var item = doc.Body.MapToClass(); + var ttype = typeof(T); + return result.Documents.Select(doc => + { + var item = (T)ttype.CreateInstanceGetDefaultValue(); + foreach (var kv in doc.Body) + { + var name = kv.Key.Replace("-", "_"); + var prop = ttype.GetPropertyOrFieldIgnoreCase(name); + if (prop == null) continue; + if (kv.Value == null) continue; + if (kv.Value is string valstr && _repository._schema.FieldsMap.TryGetValue(prop.Name, out var field) && field.FieldType == FieldType.Tag) + ttype.SetPropertyOrFieldValue(item, prop.Name, valstr.Split(new[] { (field.FieldAttribute as FtTagFieldAttribute).Separator ?? "," }, StringSplitOptions.None)); + else + ttype.SetPropertyOrFieldValue(item, prop.Name, prop.GetPropertyOrFieldType().FromObject(kv.Value)); + } var itemId = doc.Id; if (!string.IsNullOrEmpty(prefix)) if (itemId.StartsWith(prefix)) diff --git a/test/Unit/FreeRedis.Tests/RedisClientTests/ModulesTests/RediSearchTests.cs b/test/Unit/FreeRedis.Tests/RedisClientTests/ModulesTests/RediSearchTests.cs index ed0a79d..ab861be 100644 --- a/test/Unit/FreeRedis.Tests/RedisClientTests/ModulesTests/RediSearchTests.cs +++ b/test/Unit/FreeRedis.Tests/RedisClientTests/ModulesTests/RediSearchTests.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using System; using System.Diagnostics; -using System.Xml.Linq; +using System.Linq; using Xunit; namespace FreeRedis.Tests.RedisClientTests.Other @@ -51,6 +51,54 @@ class TestDoc public int Views { get; set; } } + [FtDocument("index_post100", Prefix = "blog:post:")] + class TagMapArrayIndex + { + [FtKey] + public int Id { get; set; } + + [FtTextField("title", Weight = 5.0)] + public string Title { get; set; } + + [FtTextField("category")] + public string Category { get; set; } + + [FtTextField("content", Weight = 1.0, NoIndex = true)] + public string Content { get; set; } + + [FtTagField("tags")] + public string[] Tags { get; set; } + + [FtNumericField("views")] + public int Views { get; set; } + } + [Fact] + public void TagMapArray() + { + var repo = cli.FtDocumentRepository(); + + try + { + repo.DropIndex(); + } + catch { } + repo.CreateIndex(); + + repo.Save(new TagMapArrayIndex { Id = 1, Title = "测试标题1 word", Category = "一级分类", Content = "测试内容1suffix", Tags = ["作者1","作者2"], Views = 101 }); + repo.Save(new TagMapArrayIndex { Id = 2, Title = "prefix测试标题2", Category = "二级分类", Content = "测试infix内容2", Tags = ["作者2","作者3"], Views = 201 }); + repo.Save(new TagMapArrayIndex { Id = 3, Title = "测试标题3 word", Category = "一级分类", Content = "测试word内容3", Tags = ["作者2","作者5"], Views = 301 }); + + repo.Delete(1, 2, 3); + + repo.Save(new[]{ + new TagMapArrayIndex { Id = 1, Title = "测试标题1 word", Category = "一级分类", Content = "测试内容1suffix", Tags = ["作者1","作者2"], Views = 101 }, + new TagMapArrayIndex { Id = 2, Title = "prefix测试标题2", Category = "二级分类", Content = "测试infix内容2", Tags = ["作者2","作者3"], Views = 201 }, + new TagMapArrayIndex { Id = 3, Title = "测试标题3 word", Category = "一级分类", Content = "测试word内容3", Tags = ["作者2","作者5"], Views = 301 } + }); + + var list = repo.Search(a => a.Tags.Contains("作者1")).ToList(); + } + [Fact] public void FtDocumentRepository() {