MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

Gabungkan $lookup dengan C#

Tidak perlu mengurai JSON. Semuanya di sini sebenarnya dapat dilakukan secara langsung dengan antarmuka LINQ atau Aggregate Fluent.

Hanya menggunakan beberapa kelas demonstrasi karena pertanyaannya tidak terlalu banyak untuk dilanjutkan.

Penyiapan

Pada dasarnya kami memiliki dua koleksi di sini, menjadi

entitas

{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }

dan lainnya

{
        "_id" : ObjectId("5b08cef10a8a7614c70a5712"),
        "entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
        "name" : "Sub-A"
}
{
        "_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
        "entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
        "name" : "Sub-B"
}

Dan beberapa kelas untuk mengikat mereka, seperti contoh yang sangat mendasar:

public class Entity
{
  public ObjectId id;
  public string name { get; set; }
}

public class Other
{
  public ObjectId id;
  public ObjectId entity { get; set; }
  public string name { get; set; }
}

public class EntityWithOthers
{
  public ObjectId id;
  public string name { get; set; }
  public IEnumerable<Other> others;
}

 public class EntityWithOther
{
  public ObjectId id;
  public string name { get; set; }
  public Other others;
}

Permintaan

Antarmuka Lancar

var listNames = new[] { "A", "B" };

var query = entities.Aggregate()
    .Match(p => listNames.Contains(p.name))
    .Lookup(
      foreignCollection: others,
      localField: e => e.id,
      foreignField: f => f.entity,
      @as: (EntityWithOthers eo) => eo.others
    )
    .Project(p => new { p.id, p.name, other = p.others.First() } )
    .Sort(new BsonDocument("other.name",-1))
    .ToList();

Permintaan dikirim ke server:

[
  { "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
  { "$lookup" : { 
    "from" : "others",
    "localField" : "_id",
    "foreignField" : "entity",
    "as" : "others"
  } }, 
  { "$project" : { 
    "id" : "$_id",
    "name" : "$name",
    "other" : { "$arrayElemAt" : [ "$others", 0 ] },
    "_id" : 0
  } },
  { "$sort" : { "other.name" : -1 } }
]

Mungkin yang paling mudah dipahami karena antarmuka yang lancar pada dasarnya sama dengan struktur BSON umum. $lookup stage memiliki semua argumen yang sama dan $arrayElemAt direpresentasikan dengan First() . Untuk $sort Anda cukup menyediakan dokumen BSON atau ekspresi valid lainnya.

Alternatifnya adalah bentuk ekspresif yang lebih baru dari $lookup dengan pernyataan sub-pipa untuk MongoDB 3.6 dan di atasnya.

BsonArray subpipeline = new BsonArray();

subpipeline.Add(
  new BsonDocument("$match",new BsonDocument(
    "$expr", new BsonDocument(
      "$eq", new BsonArray { "$$entity", "$entity" }  
    )
  ))
);

var lookup = new BsonDocument("$lookup",
  new BsonDocument("from", "others")
    .Add("let", new BsonDocument("entity", "$_id"))
    .Add("pipeline", subpipeline)
    .Add("as","others")
);

var query = entities.Aggregate()
  .Match(p => listNames.Contains(p.name))
  .AppendStage<EntityWithOthers>(lookup)
  .Unwind<EntityWithOthers, EntityWithOther>(p => p.others)
  .SortByDescending(p => p.others.name)
  .ToList();

Permintaan dikirim ke server:

[ 
  { "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
  { "$lookup" : {
    "from" : "others",
    "let" : { "entity" : "$_id" },
    "pipeline" : [
      { "$match" : { "$expr" : { "$eq" : [ "$$entity", "$entity" ] } } }
    ],
    "as" : "others"
  } },
  { "$unwind" : "$others" },
  { "$sort" : { "others.name" : -1 } }
]

"Builder" yang Lancar belum mendukung sintaks secara langsung, ekspresi LINQ juga tidak mendukung $expr operator, namun Anda masih dapat membuat menggunakan BsonDocument dan BsonArray atau ekspresi valid lainnya. Di sini kita juga "ketik" $unwind hasil untuk menerapkan $sort menggunakan ekspresi daripada BsonDocument seperti yang ditunjukkan sebelumnya.

Selain kegunaan lain, tugas utama "sub-pipeline" adalah mengurangi dokumen yang dikembalikan dalam larik target $lookup . Juga $unwind di sini berfungsi untuk benar-benar "digabungkan" ke dalam $lookup pernyataan pada eksekusi server, jadi ini biasanya lebih efisien daripada hanya mengambil elemen pertama dari larik yang dihasilkan.

Gabung ke Grup yang Dapat Dikueri

var query = entities.AsQueryable()
    .Where(p => listNames.Contains(p.name))
    .GroupJoin(
      others.AsQueryable(),
      p => p.id,
      o => o.entity,
      (p, o) => new { p.id, p.name, other = o.First() }
    )
    .OrderByDescending(p => p.other.name);

Permintaan dikirim ke server:

[ 
  { "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
  { "$lookup" : {
    "from" : "others",
    "localField" : "_id",
    "foreignField" : "entity",
    "as" : "o"
  } },
  { "$project" : {
    "id" : "$_id",
    "name" : "$name",
    "other" : { "$arrayElemAt" : [ "$o", 0 ] },
    "_id" : 0
  } },
  { "$sort" : { "other.name" : -1 } }
]

Ini hampir identik tetapi hanya menggunakan antarmuka yang berbeda dan menghasilkan pernyataan BSON yang sedikit berbeda, dan benar-benar hanya karena penamaan yang disederhanakan dalam pernyataan fungsional. Ini memunculkan kemungkinan lain hanya dengan menggunakan $unwind seperti yang dihasilkan dari SelectMany() :

var query = entities.AsQueryable()
  .Where(p => listNames.Contains(p.name))
  .GroupJoin(
    others.AsQueryable(),
    p => p.id,
    o => o.entity,
    (p, o) => new { p.id, p.name, other = o }
  )
  .SelectMany(p => p.other, (p, other) => new { p.id, p.name, other })
  .OrderByDescending(p => p.other.name);

Permintaan dikirim ke server:

[
  { "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
  { "$lookup" : {
    "from" : "others",
    "localField" : "_id",
    "foreignField" : "entity",
    "as" : "o"
  }},
  { "$project" : {
    "id" : "$_id",
    "name" : "$name",
    "other" : "$o",
    "_id" : 0
  } },
  { "$unwind" : "$other" },
  { "$project" : {
    "id" : "$id",
    "name" : "$name",
    "other" : "$other",
    "_id" : 0
  }},
  { "$sort" : { "other.name" : -1 } }
]

Biasanya menempatkan $unwind langsung mengikuti $lookup sebenarnya adalah "pola yang dioptimalkan" untuk kerangka kerja agregasi. Namun driver .NET mengacaukannya dalam kombinasi ini dengan memaksa $project di antara daripada menggunakan penamaan tersirat pada "as" . Jika bukan karena itu, ini sebenarnya lebih baik daripada $arrayElemAt ketika Anda tahu Anda memiliki "satu" hasil terkait. Jika Anda ingin $unwind "coalescence", maka Anda lebih baik menggunakan antarmuka yang lancar, atau bentuk lain seperti yang ditunjukkan nanti.

Alami yang Luar Biasa

var query = from p in entities.AsQueryable()
            where listNames.Contains(p.name) 
            join o in others.AsQueryable() on p.id equals o.entity into joined
            select new { p.id, p.name, other = joined.First() }
            into p
            orderby p.other.name descending
            select p;

Permintaan dikirim ke server:

[
  { "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
  { "$lookup" : {
    "from" : "others",
    "localField" : "_id",
    "foreignField" : "entity",
    "as" : "joined"
  } },
  { "$project" : {
    "id" : "$_id",
    "name" : "$name",
    "other" : { "$arrayElemAt" : [ "$joined", 0 ] },
    "_id" : 0
  } },
  { "$sort" : { "other.name" : -1 } }
]

Semua cukup akrab dan benar-benar hanya sampai ke penamaan fungsional. Sama seperti menggunakan $unwind pilihan:

var query = from p in entities.AsQueryable()
            where listNames.Contains(p.name) 
            join o in others.AsQueryable() on p.id equals o.entity into joined
            from sub_o in joined.DefaultIfEmpty()
            select new { p.id, p.name, other = sub_o }
            into p
            orderby p.other.name descending
            select p;

Permintaan dikirim ke server:

[ 
  { "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
  { "$lookup" : {
    "from" : "others",
    "localField" : "_id",
    "foreignField" : "entity",
    "as" : "joined"
  } },
  { "$unwind" : { 
    "path" : "$joined", "preserveNullAndEmptyArrays" : true
  } }, 
  { "$project" : { 
    "id" : "$_id",
    "name" : "$name",
    "other" : "$joined",
    "_id" : 0
  } }, 
  { "$sort" : { "other.name" : -1 } }
]

Yang sebenarnya menggunakan bentuk "penggabungan yang dioptimalkan". Penerjemah masih bersikeras untuk menambahkan $project karena kita memerlukan select intermediate perantara untuk membuat pernyataan tersebut valid.

Ringkasan

Jadi ada beberapa cara untuk sampai pada apa yang pada dasarnya adalah pernyataan kueri yang sama dengan hasil yang persis sama. Sementara Anda "bisa" mengurai JSON ke BsonDocument bentuk dan masukkan ini ke Aggregate() . yang lancar perintah, umumnya lebih baik menggunakan pembangun alami atau antarmuka LINQ karena mereka dengan mudah memetakan ke pernyataan yang sama.

Opsi dengan $unwind sebagian besar ditampilkan karena bahkan dengan kecocokan "tunggal" bentuk "koalesensi" itu sebenarnya jauh lebih optimal daripada menggunakan $arrayElemAt untuk mengambil elemen larik "pertama". Ini bahkan menjadi lebih penting dengan pertimbangan hal-hal seperti Batas BSON di mana $lookup array target dapat menyebabkan dokumen induk melebihi 16MB tanpa pemfilteran lebih lanjut. Ada posting lain di sini di Agregat $lookup Ukuran total dokumen dalam pipa yang cocok melebihi ukuran dokumen maksimum di mana saya benar-benar membahas bagaimana menghindari batas itu dipukul dengan menggunakan opsi seperti itu atau Lookup() lainnya sintaks hanya tersedia untuk antarmuka yang lancar saat ini.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Bagaimana cara saya meminta nilai yang berbeda di Mongoose?

  2. Percona Live 2017 - Rekap Somenines

  3. Tidak dapat mengautentikasi di mongodb dengan PHP

  4. pengurutan MongoDB()

  5. Peta Hadoop/Kurangi vs Peta bawaan/Kurangi