在TransformResults中访问字典失败

时间:2013-01-22 04:34:06

标签: ravendb

鉴于此文档类:

    public class Product
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public SpecialType? DefaultOffer { get; set; }
        public Dictionary<SpecialType, string> Specials { get; set; }
    }

    public enum SpecialType
    {
        Something1,
        Something2
    }

我希望从上面的文档中提出这个视图模型:

    public class ProductSummary
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string SpecialOffer { get; set; }
    }

我创建了以下索引:

    public class ProductSummaries : AbstractIndexCreationTask<Product>
    {
        public ProductSummaries()
        {
            Map = products => from p in products
                              select new { p.Id, p.Name, p.DefaultOffer, p.Specials };

            TransformResults = (db, products) =>
                                from p in products
                                select new
                                {
                                    Id = p.Id,
                                    Name = p.Name,
                                    SpecialOffer = p.Specials[p.DefaultOffer.Value]
                                };
        }
    }

简单来说,我希望视图模型使用Specials字典中的任何字符串由DefaultOffer的当前值指示。

以下单元测试失败:

    [TestMethod]
    public void CanIndexIntoDictionary()
    {
        using (var documentStore = this.GetDocumentStore())
        {
            documentStore.ExecuteIndex(new ProductSummaries());

            // Store some documents
            using (var session = documentStore.OpenSession())
            {
                session.Store(new Product 
                { 
                    Id = "products/2", 
                    Name = "B", 
                    Specials = new Dictionary<SpecialType, string> 
                    { 
                        { SpecialType.Something1, "B1" }, 
                        { SpecialType.Something2, "B2" } 
                    }, 
                    DefaultOffer = SpecialType.Something2 
                 });
                 session.SaveChanges();
            }

            // Make sure it got persisted correctly
            using (var session = documentStore.OpenSession())
            {
                var b = session.Load<Product>("products/2");
                Assert.AreEqual("B2", b.Specials[b.DefaultOffer.Value]); // PASSES
            }

            // Now query and transform
            using (var session = documentStore.OpenSession())
            {
                var result = session.Query<Product, ProductSummaries>()
                    .Customize(x => x.WaitForNonStaleResults())
                    .AsProjection<ProductSummary>()
                    .ToList();

                Assert.AreEqual(1, result.Count);
                Assert.AreEqual("B2", result.First().SpecialOffer); // FAILS - actual is NULL
            }
        }
    }

我需要做什么才能让这个测试通过?

*更新*

通过使用Matt的建议(在下面的评论中),使Enum值代表NONE,我们可以修改他的答案并摆脱可以为空的枚举。整个模型和索引看起来更清洁。

    public enum SpecialType
    {
        None = 0,
        Something1,
        Something2
    }

    public class Product
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public SpecialType DefaultOffer { get; set; }
        public Dictionary<SpecialType, string> Specials { get; set; }
    }

    public class ProductSummaries : AbstractIndexCreationTask<Product,ProductSummary>
    {
        public ProductSummaries()
        {
            Map = products => from p in products
                              select new { p.Name, SpecialOffer = p.Specials[p.DefaultOffer] };

            Store(x => x.SpecialOffer, FieldStorage.Yes);
        }
    }

有趣的是,这个索引消除了对空检查等的需要,因为当SpecialOffer不是p.DefaultOffer字典中包含的密钥时,RavenDB只会将Specials设置为null。 (仅当p.Name包含在Map中时才会出现这种情况。)

1 个答案:

答案 0 :(得分:1)

索引中不需要TransformResults部分。事实上,你正在映射你不需要的东西。

  • Id始终是隐式映射的。它在索引中变为__document_id,raven将其正确挂钩。
  • Name只需要映射,如果您要对其进行过滤或排序,那么您在测试中没有这样做。如果你需要现实世界,你可以把它放回去。
  • Name类似,如果您要将其用于其他目的,则只需映射DefaultOfferSpecials。为了演示并通过单元测试,我已将其删除。

这里唯一需要的诀窍是由于可以为空的枚举。因此,Raven很难从c#翻译您的查询。您可以通过一些广告空检查和AsDocument方法的使用来解决这个问题:

public class ProductSummaries : AbstractIndexCreationTask<Product, ProductSummary>
{
    public ProductSummaries()
    {
        Map = products => from p in products
                          let defaultOffer = AsDocument(p).Value<string>("DefaultOffer")
                          select new
                          {
                              SpecialOffer = defaultOffer == null ? null : AsDocument(p.Specials)[defaultOffer]
                          };

        Store(x => x.SpecialOffer, FieldStorage.Yes);
    }
}

另请注意,您之前在特价商品字段上获取null的原因是因为您尝试从索引中投影它但它不是存储字段。为该字段打开字段存储将解决问题的这一部分。

您不需要存储任何其他字段,因为它们已经存在于文档中 - 这也是投影数据的来源。

BTW - 感谢单元测试。它使调试和快速回答变得更加容易。 :)