1

给定这个文档类:

    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
            }
        }
    }

我需要做什么才能通过此测试?

* 更新 *

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

    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);
        }
    }

有趣的是,这个索引消除了对空值检查等的需要,因为当不是字典中包含的键时,RavenDB 简单地设置SpecialOffer为空值。(仅当 p.Name 包含在 Map 中时才适用。)p.DefaultOfferSpecials

4

1 回答 1

1

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

  • Id总是隐式映射。它__document_id在索引中,并且 raven 将其适当地连接起来。
  • Name仅当您要过滤或排序时才需要映射,而您在测试中没有这样做。如果您在现实世界中需要它,您可以将其放回原处。
  • 与 类似Name,您只需要映射DefaultOffer以及Specials是否要将它们用于其他目的。为了演示和通过您的单元测试,我删除了它们。

这里唯一需要的技巧是由于可以为空的枚举。因此,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 的原因是因为您试图从索引中投影它,但它不是存储字段。为该字段打开字段存储将解决该部分问题。

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

顺便说一句 - 感谢单元测试。它使调试和快速响应变得更加容易。:)

于 2013-01-22T16:46:09.527 回答