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 値を持つというマットの提案 (以下のコメント) を使用することで、彼の答えを変更し、null 許容列挙型を取り除くことができます。モデル全体とインデックスがよりきれいに見えます。

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

興味深いことに、このインデックスを使用すると、null チェックなどの必要がなくなります。RavenDBは、キーがディクショナリに含まれていないSpecialOffer場合に null に設定するだけだからです。(これは、p.Name がマップに含まれている場合にのみ当てはまります。)p.DefaultOfferSpecials

4

1 に答える 1

1

TransformResultsインデックスにセクションは必要ありません。実際、必要のないかなりの量をマッピングしています。

  • Id常に暗黙的にマップされます。インデックスになっ__document_idてカラスが適当に引っ掛けます。
  • Nameテストで行っていないフィルターまたは並べ替えを行う場合にのみ、マップする必要があります。現実世界で必要な場合は、元に戻すことができます。
  • と同様に、他の目的で使用する場合は、Nameマッピングするだけで済みます。デモンストレーションと単体テストに合格するために、それらを削除しました。DefaultOfferSpecials

ここで必要な唯一のトリッキーは、null 許容列挙型によるものです。そのため、Raven はクエリを C# から翻訳するのに苦労しています。AsDocumentいくつかの創造的な null チェックとメソッドの使用で回避できます。

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 に答える