【问题标题】:Sitecore7 Linq to Sitecore only works with SearchResultItem but not with Custom Mapped ClassSitecore7 Linq to Sitecore 仅适用于 SearchResultItem 但不适用于自定义映射类
【发布时间】:2014-06-19 07:58:16
【问题描述】:

我有一个非常奇怪的问题,我无法理解。也许有人可以指出我做错了什么。

基本上,我只是尝试使用 Linq to Sitecore 搜索项目。

所以,我的课程看起来像(我也在使用玻璃)

[SitecoreType(TemplateId = "{TEMPLATE_GIUD}")]
public class MyMappedClass : SharedFieldClass
{
    [SitecoreField(FieldName = "mylist")]
    public virtual IEnumerable<SharedFieldClass> MyMultilistField { get; set; }


    [SitecoreField(FieldName = "field1")]
    [IndexField("field1")]
    public virtual MyKeyValue field1 { get; set; }    
}
[SitecoreType]
public class MyKeyValue
{
    public virtual Sitecore.Data.ID Id {get;set;}    
    public virtual string MyValue{get;set;}
}

所以当我执行以下查询时,它会按预期工作。

    var results = context.GetQueryable<SearchResultItem>()
                  .Where(c => ((string)c["field1"]) == "{GUID}").GetResults();

但是,当我执行以下操作时,它会返回 0 结果。

List<MyMappedClass> results = context.GetQueryable<MyMappedClass>()
                              .Where(c => c.field1.MyValue == "{GUID}").ToList();

我已阅读 this link 。我已经按照 here 描述的第二个流程让 Glass 与 Sitecore7 Search 一起工作(“SharedFieldClass”包含所有基本索引字段)。

这是一个非常明显的场景,我相信很多人已经这样做了,而我在这里做了一些愚蠢的事情。

提前致谢。

/## 编辑##/

好的,所以我对此进行了更多挖掘。不确定这是否是 ContentSearch/Luncene.NET API 中的错误,或者我遗漏了一些东西,但似乎 here 发布的内容可能不是 TRUE,如果您有一个复杂的字段类型(您会这样做),您无法使用针对 Lucene 的映射类。 (不确定 Solr 是否也是这种情况)。如果您正在对字符串和 int 等简单类型进行搜索,那么它就像一个魅力。

这是我的发现:

  1. 在为 contentsearch 启用 DEBUG 和 LOG 后,我发现如果我像这样查询context.GetQueryable&lt;MyMappedClass&gt;().Where(c =&gt; c.field1.MyValue == "{GUID}"),它会被翻译成 DEBUG Executing lucene query: field1.value:7e9ed2ae07194d83872f9836715eca8e,并且在名为“field1.value”的索引中没有这样的东西,因此查询不会不返回任何东西。索引的名称实际上只是“field1”。 这是一个错误吗??
  2. 但是,像 context.GetQueryable&lt;SearchResultItem&gt;() .Where(c =&gt; ((string)c["field1"]) == "{GUID}").GetResults(); 这样的查询可以工作,因为它会被翻译成 "DEBUG Executing lucene query: +field1:7e9ed2ae07194d83872f9836715eca8e"
  3. 我也确实在我的映射类中编写了一个方法,如下所示:

    public string this[string key]
    {
        get
        {
            return key.ToLowerInvariant();
        }
        set { }
    }
    

这让我可以使用 MyMappedClass 编写以下查询。

results2 = context.GetQueryable<MyMappedClass>().Where(c => c["filed1"]== "{GUID}").ToList();

这返回了预期的结果。 BUT MyMappedClass 中的字段值没有被填充(实际上它们都是空的,除了在填充的文档中填充的核心/共享值,如 templateid、url 等)。所以结果列表几乎没有用。我可以对所有这些进行循环并手动获取填充的值,因为我有 itemid。但是想象一下大型结果集的成本。

最后我这样做了:

<fieldType fieldTypeName="droplink"                           storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.String"   settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" />

因此,在使用“IndexViewer2.0”的 lucene 查询中返回了填充的“field1”和 itemid。 但是这对于 MyMappedClass 也失败了,因为文档中“field1”的值是一个 System.string .. 但它在 MyMappedClass SO 中被映射为“MyKeyValue”它抛出以下异常

Exception: System.InvalidCastException
Message: Invalid cast from 'System.String' to 'MyLib.MyKeyValue'.

所以,最大的问题是: 如何使用酷炫的 ContentSearch API 使用他/她的映射类进行查询?

【问题讨论】:

  • 尝试将 ContentSearch.EnableSearchDebug 设置为 true 然后将 logger "name="Sitecore.Diagnostics.Search" 级别更改为 DEBUG,这将记录您翻译的 lucene 查询,您可以检查有什么问题它
  • 嗨@AhmedOkour,感谢您的提示。这有助于进一步挖掘。请参阅我编辑的部分。
  • 我遇到了完全相同的问题 - 没有找到任何其他尝试这样做的人的参考资料。目前,我正在使用自定义 TypeConverter,它可以填充属性,但它没有 .Where 过滤语法起作用。
  • @tomunderhill 在其中一个答案中查看我的工作。我没有将其标记为“正确答案”,因为我想先尝试 Michael Edward 的解决方案,在这种情况下,我仍然面临一些关于 contentsearchindex(web) 中填充的索引值的问题。所以基本上我现在面临的是我在我的真实代码中的 where 子句中放置了 2 个条件。其中一个字段已填充,但另一个字段未填充,即使它们都是 droplink 字段类型(我猜这与索引本身有关......有什么想法吗?)。很快就会在这里发布我的发现。

标签: linq sitecore sitecore7 linq-query-syntax glass-mapper


【解决方案1】:

我进一步挖掘得到了一个可行的解决方案。在这里发布它以防万一有人遇到这个问题。

这就是我的“SharedFieldClass”的样子(这有点不对)

public abstract class SharedFieldClass
{
    [SitecoreId]
    [IndexField("_id")]
    [TypeConverter(typeof(IndexFieldIDValueConverter))]
    public virtual ID Id { get; set; }

    [SitecoreInfo(SitecoreInfoType.Language)]
    [IndexField("_language")]
    public virtual string Language { get; set; }

    [SitecoreInfo(SitecoreInfoType.Version)]
    public virtual int Version
    {
        get
        {
            return Uri == null ? 0 : Uri.Version.Number;
        }
    }

    [TypeConverter(typeof(IndexFieldItemUriValueConverter))]
    [XmlIgnore]
    [IndexField("_uniqueid")]
    public virtual ItemUri Uri { get; set; }
}

Glass 中有一个类可以进行映射。如下所示:

var sitecoreService = new SitecoreService("web");
foreach (var r in results)
{
    sitecoreService.Map(r);
}

对我来说,这个类因为这条线而无法映射:

        [SitecoreId]
        [IndexField("_id")]
        [TypeConverter(typeof(IndexFieldIDValueConverter))]
        public virtual ID Id { get; set; }

它在 sitecoreService.Map(r) 处抛出 NULL 异常;线 所以我把它改成下面:

    [SitecoreId]
    [IndexField("_group")]
    [TypeConverter(typeof(IndexFieldIDValueConverter))]
    public virtual ID Id { get; set; }

它奏效了。我不确定 sitecore 中 ItemId 的索引字段何时从“_id”更改为“_group”,或者它是否总是这样。但它是 SearchResultItem 类中的“_group”。所以我使用它并且映射成功。

所以总结一下,所有的解决方案看起来像这样:

映射类:

[SitecoreType(TemplateId = "{TEMPLATE_GIUD}")]
public class MyMappedClass : SharedFieldClass
{
    [SitecoreField(FieldName = "mylist")]
    public virtual IEnumerable<SharedFieldClass> MyMultilistField { get; set; }


    [SitecoreField(FieldName = "field1")]
    [IndexField("field1")]
    public virtual MyKeyValue field1 { get; set; }    

    // Will be set with key and value for each field in the index document
    public string this[string key]
    {
        get
        {
            return key.ToLowerInvariant();
        }
        set { }
    }
}
[SitecoreType]
public class MyKeyValue
{
    public virtual Sitecore.Data.ID Id {get;set;}    
    public virtual string MyValue{get;set;}
}

查询是:

List<MyMappedClass> results = context.GetQueryable<MyMappedClass>()
                              .Where(c => c["field1"] == "{GUID}").ToList();

就是这样。

/* 已编辑 */

哦!!等等,不是这样。当“field1”在索引文档中填充了 guid 时,这仍然会失败转换复杂类型“MyKeyValue”。

所以为了避免这种情况,我不得不按照@Michael Edwards 的建议编写我的自定义转换器。

我不得不稍微修改课程以满足我的需要..

public class IndexFieldKeyValueModelConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        var config = Glass.Mapper.Context.Default.GetTypeConfiguration<SitecoreTypeConfiguration>(sourceType, true);
        if (config != null && sourceType == typeof(MyLib.IKeyValue))
        {
            return true;
        }
        else
            return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(string))
            return true;
        else
            return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        var scContext = new SitecoreContext();
        Guid x = Guid.Empty;
        if(value is string)
        {
            x = new Guid((string)value);
        }

        var item = scContext.Database.GetItem(x.ToString());
        if (item == null)
            return null;
        return scContext.CreateType(typeof(MyLib.IKeyValue), item, true, false, new Dictionary<string, object>());
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        var config = Glass.Mapper.Context.Default.GetTypeConfiguration<SitecoreTypeConfiguration>(value.GetType(), true);
        ID id = config.GetId(value);
        return id.ToShortID().ToString().ToLowerInvariant();
    }
}

不确定这是否是预期的行为.. 但由于某种原因,在 convertfrom 方法中,“object value”参数的值是短字符串 id 格式。因此,为了让 scContext.Database.GetItem 工作,我必须将其转换为正确的 GUID,然后它开始返回正确的项目而不是 null。

然后我这样写了我的查询:

results = context.GetQueryable<MyMappedGlassClass>().Where(c => c["field1"] == field1value && c["field2"] == field2value && c["_template"] == templateId).Filter(selector => selector["_group"] != currentId).ToList();

看起来需要做很多工作才能让它发挥作用。我想使用 LinqHelper.CreateQuery 方法是最简单的方法.. 但正如 Pope 先生建议的here 那样,不要使用此方法,因为这是一种内部方法。

不确定这里的余额是多少。 /* 结束编辑 */

请参阅我的问题描述部分,了解为什么我必须以这种方式做事。

另外,(我有偏见)是here 描述的技巧可能不再有效(请参阅我的问题描述的编辑部分了解背后的原因)。

此外,我认为 Glass Mapper 教程 here 中 itemid 的索引字段是错误的(除非另有证明)。

希望它可以帮助某人节省/浪费时间。

【讨论】:

    【解决方案2】:

    您可以为 lucene 创建一个自定义字段映射器,它将索引中的 Guid 转换为 glass 模型。我破解了这个,但我还没有测试过:

    public class IndexFieldDateTimeValueConverter : TypeConverter
    {
    
        public Type RequestedType { get; set; }
    
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            var config = Glass.Mapper.Context.Default.GetTypeConfiguration<SitecoreTypeConfiguration>(sourceType, true);
            if (config != null)
            {
                RequestedType = sourceType;
                return true;
            }
            else
                return base.CanConvertFrom(context, sourceType);
        }
    
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(string))
                return true;
            else
                return base.CanConvertTo(context, destinationType);
        }
    
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var scContext = new SitecoreContext();
            return scContext.CreateType(RequestedType,  scContext.Database.GetItem(value.ToString()),true, false, new Dictionary<string, object>());
    
    
        }
    
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            var config = Glass.Mapper.Context.Default.GetTypeConfiguration<SitecoreTypeConfiguration>(value.GetType(), true);
            ID id =config.GetId(value);
            return id.ToShortID().ToString().ToLowerInvariant();
        }
    

    我担心的是 ConvertFrom 方法没有传递请求的类型,因此我们必须将其作为属性存储在类中,以便将它从 CanConvertFrom 方法传递到 ConvertFrom 方法。这使得这个类不是线程安全的。

    将此添加到站点核心配置的 indexFieldStorageValueFormatter 部分。

    【讨论】:

    • 嗨,迈克,我遇到了同样的问题,并尝试使用自定义 TypeConverter 沿着这条路线走。我的版本适用于在搜索中填充复杂类型属性,但不适用于过滤这些属性的搜索。有什么想法吗?我认为这是因为这里提到的 Linq 解析功能:kamsar.net/index.php/2013/04/sitecore-7-linq-gotchas
    • 我自己尝试过实现这一点,您能否提供并举例说明如何将其用作过滤器。我怀疑这需要对 linq 解析器有深入的了解。我们可能需要和波普先生谈谈。
    【解决方案3】:

    这里的问题是SearchResultItem 实际上不是Item,但确实有GetItem() 方法来获取Sitecore 项目。您需要做的是:

    List<MyMappedClass> results = context.GetQueryable<SearchResultItem>()
        .Select(sri => sri.GetItem())
        .Where(i => i != null)
        .Select(i => i.GlassCast<MyMappedClass>())
        .Where(c => c.field1.MyValue == "{GUID}").ToList();
    

    【讨论】:

    • 我无法编译您的方法。它在“.Select(i => i.GlassCast())”行中出错。
    • 在该行中,您将每个Sitecore.Context.Item 转换为MyMappedClass。无论你采取什么方法来进行投射,都应该在这里完成。
    【解决方案4】:

    我没有专门使用 Glass,但是如果您将父类更改为 SearchResultItem,它会开始工作吗?如果是这样,则表明 SharedFieldClass 父类存在问题。

    【讨论】:

    • 我尝试使用 "MyMappedSearchClass:SearchResultItem" 并查询 c=>c["field1"] == "{GUID}" .. 并遇到了我在编辑部分中提到的相同问题,例如字段值为空。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-30
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多