3

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

基本上,我只是想使用 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();

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

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

提前致谢。

/## 编辑 ##/

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

所以这是我的发现:

  1. 在为 contentsearch 启用 DEBUG 和 LOG 后,我发现如果我像这样查询context.GetQueryable<MyMappedClass>().Where(c => c.field1.MyValue == "{GUID}")它会被翻译成什么,DEBUG Executing lucene query: field1.value:7e9ed2ae07194d83872f9836715eca8e并且在名为“field1.value”的索引中没有这样的东西,查询不会返回任何内容。索引的名称实际上只是“field1”。这是一个错误吗?
  2. 但是,这样的查询context.GetQueryable<SearchResultItem>() .Where(c => ((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();

这返回了预期的结果。但是未填充 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中被映射为“MyKeyValue”所以它抛出以下异常

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

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

4

4 回答 4

6

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

这就是我的“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 先生在这里建议的那样,不要使用这种方法,因为这是一种内部方法。

不知道这里的平衡是什么。/* 结束编辑 */

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

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

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

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

于 2014-06-20T06:34:08.443 回答
4

您可以为 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 方法。这使得这个类不是线程安全的。

将此添加到 sitecore 配置的 indexFieldStorageValueFormatter 部分。

于 2014-06-20T06:38:25.520 回答
1

这里的问题是它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();
于 2014-06-19T15:50:54.027 回答
0

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

于 2014-06-19T13:10:45.067 回答