RavenDB多对多和索引

时间:2015-04-26 01:36:46

标签: c# ravendb

需要RavenDB的帮助。

在我的网页中,我希望有这样的列表:

  • item1 category1
  • item2 category2
  • ...

另一个:

  • category1,项目数
  • category2,item of items
  • ...

我的数据结构:

public class Item
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string CategoryId { get; set; }
}


public class Category
{
    public string Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

第一个清单的索引:

public class Item_WithCategory : AbstractIndexCreationTask<Item>
{
    public class Result
    {
        public string Name { get; set; }
        public string CategoryName { get; set; }
    }

    public Item_WithCategory()
    {
        Map = items => from item in items
              select new
                  {
                      Name = item.Name,
                      CategoryName = LoadDocument<Category>(item.CategoryId).Name
                  };
    }
}

此数据结构是否适合我的情况,或者在项目结构中使用Category代替CategoryId会更好吗?

我应该使用索引还是有更好的解决方案来获取类别名称?

如果我的索引是好的,如何编写正确的查询?我目前的尝试:

    Item_WithCategory.Result[] all;
    using (var session = DocumentStoreHolder.Store.OpenSession())
    {
        all = session.Query<Item_WithCategory.Result, Item_WithCategory>().ToArray();
    }

但它会抛出异常,说明返回类型是项目,而不是结果。如何解决?

1 个答案:

答案 0 :(得分:1)

你有几个选择。您可以在Item实体上存储CategoryId和CategoryName。这当然会导致重复的数据(如果你仍然需要存储类别实体),但“存储很便宜”这些天是一个流行的术语。这样做的缺点是你需要更新给定类别的每个Item文档如果类别名称更改以保持一致。好处是您需要做更少的工作才能获得理想的结果。

如果您在项目上存储类别名称,则不需要特殊索引来处理第一个列表,只需查询项目并返回所需内容。对于第二个列表,您需要在类别上分组的Item实体上创建Map / Reduce索引。

但是,如果您需要使用您提供的数据结构,有几种方法可以解决这个问题。首先,我们不建议在索引定义中使用LoadDocument,尤其是在select语句中。这可能会以负面的方式影响索引性能。

相反,只需索引您需要查询的属性(或使用自动索引),然后使用Result Transformer从相关文档中获取信息:

public class ItemCategoryTransformer : AbstractTransformerCreationTask<Item>
{
    public ItemCategoryTransformer()
    {
        TransformResults = results => from item in results
                                      let category = LoadDocument<Category>(item.CategoryId)
                                      select new ItemCategoryViewModel
                                      {
                                          Name = item.Name,
                                          CategoryName = category.Name
                                      };
    }
}

public class ItemCategoryViewModel
{
    public string Name { get; set; }
    public string CategoryName { get; set; }
}

您可以将此Transformer与Item实体的查询一起使用:

using (var session = documentStore.OpenSession())
{
    var items = session.Query<Item>()
                        .TransformWith<ItemCategoryTransformer, ItemCategoryViewModel>()
                        .ToList();
}

至于第二个列表,仍在使用您的数据结构,您必须使用一些东西。首先,对Items进行Map / Reduce索引,按CategoryId分组:

public class Category_Items_Count : AbstractIndexCreationTask<Item, Category_Items_Count.Result>
{
    public class Result
    {
        public string CategoryId { get; set; }
        public int Count { get; set; }
    }

    public Category_Items_Count()
    {
        Map = items => from item in items

            select new Result
            {
                CategoryId = item.CategoryId,
                Count = 1
            };

        Reduce = results => from result in results
            group result by result.CategoryId
            into c
            select new Result
            {
                CategoryId = c.Key,
                Count = c.Sum(x => x.Count)
            };
    }
}

但由于您在Item实体上只有CategoryId,您必须使用与第一个列表中类似的变换器:

public class CategoryItemsCountTransformer : AbstractTransformerCreationTask<Category_Items_Count.Result>
{
    public CategoryItemsCountTransformer()
    {
        TransformResults = results => from result in results
            let category = LoadDocument<Category>(result.CategoryId)
            select new CategoryItemsCountViewModel
            {
                CategoryName = category.Name,
                NumberOfItems = result.Count
            };
    }
}

public class CategoryItemsCountViewModel
{
    public string CategoryName { get; set; }
    public int NumberOfItems { get; set; }
}

最后,像这样查询:

using (var session = documentStore.OpenSession())
{
    var items = session.Query<Category_Items_Count.Result, Category_Items_Count>()
                       .TransformWith<CategoryItemsCountTransformer, CategoryItemsCountViewModel>()
                       .ToList();
}

正如您所看到的,根据您使用的数据结构,所需的工作存在很大差异。如果您直接在Item实体上存储了Category Name,则不需要任何Result Transformers来实现您所获得的结果(您只需要Map / Reduce索引)。

但是,结果转换器是在服务器端执行的,只在请求时执行,而不是在每次索引发生时执行的索引中使用LoadDocument。此外,也许为什么不建议在索引定义中使用LoadDocuments,对索引中使用LoadDocument引用的文档的每次更改都将导致必须重写该索引。这可能会导致索引引擎的大量工作。

最后,回答你关于为什么在查询时遇到异常的最后一个问题:因为索引的实际返回类型是被索引的文档(在本例中为Item)。要使用其他东西,您需要将结果投射到其他内容。这可以通过在查询中使用“.As()”来完成:

Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
    all = session.Query<Item_WithCategory.Result, Item_WithCategory>()
        .As<Item_WithCategory.Result>()         
        .ToArray();
}

很长的帖子,但希望它有所帮助!