鉴于此文档类:
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中时才会出现这种情况。)
答案 0 :(得分: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的原因是因为您尝试从索引中投影它但它不是存储字段。为该字段打开字段存储将解决问题的这一部分。
您不需要存储任何其他字段,因为它们已经存在于文档中 - 这也是投影数据的来源。
BTW - 感谢单元测试。它使调试和快速回答变得更加容易。 :)