Sintaks yang lancar untuk metode ekstensi IQueryable?

Saya akan membiarkan kodenya berbicara sendiri:

public interface ISoftDeletable {
  bool IsDeleted {get; set;}
}

public static class Extensions {
  public IQueryable<T> Active<T>(this IQueryable<T> q) where T : ISoftDeletable {
    return q.Where(t => !t.IsDeleted);
  }
}

public partial class Thing : ISoftDeletable {
  ...
}

...
var query = from tc in db.ThingContainers
            where tc.Things.Active().Any(t => t.SatisfiesOtherCondition)
            select new { ... }; // throws System.NotSupportedException

Kesalahannya adalah:

LINQ ke Entitas tidak mengenali metode metode 'System.Collections.Generic.IQueryable`1[Thing] ActiveThing', dan metode ini tidak dapat diterjemahkan ke dalam ekspresi penyimpanan.

Anda mendapatkan idenya: Saya ingin cara yang lancar untuk mengekspresikan hal ini sehingga untuk ISoftDeletable apa pun saya dapat menambahkan klausa 'di mana' dengan potongan kode sederhana yang dapat digunakan kembali. Contoh di sini tidak berfungsi karena Linq2Entities tidak tahu apa yang harus dilakukan dengan metode Active() saya.

Contoh yang saya berikan di sini sederhana, tetapi dalam kode asli saya, ekstensi Active() berisi serangkaian kondisi yang jauh lebih rumit, dan saya tidak ingin menyalin dan menempelkannya ke seluruh kode saya.

Ada saran?


person Shaul Behr    schedule 29.08.2013    source sumber
comment
Anda belum mengatakan apa pengecualian yang Anda dapatkan, sehingga lebih sulit untuk membantu Anda. Saya berharap apa yang telah Anda lakukan sejauh ini baik-baik saja - tetapi contoh yang lebih rumit tidak, misalnya. menggunakan Active dalam gabungan.   -  person Jon Skeet    schedule 29.08.2013
comment
@JonSkeet - menambahkan pesan kesalahan. Tapi ya, Anda benar, penggunaan saya yang sebenarnya memang melibatkan penggabungan.   -  person Shaul Behr    schedule 29.08.2013
comment
Dan apakah Anda sudah mereproduksi kesalahan itu dengan kode sederhana Anda? Saya kira tidak - akan berguna jika memiliki kode contoh yang tidak mereproduksi kesalahan.   -  person Jon Skeet    schedule 29.08.2013
comment
@JonSkeet - diedit untuk menunjukkan sesuatu yang mendekati kode saya yang sebenarnya. Jelas saya tidak dapat mengkompilasi untuk melihat apakah ini benar-benar gagal, tetapi ini lebih baik daripada memberikan kode saya yang sebenarnya kepada Anda!   -  person Shaul Behr    schedule 29.08.2013
comment
@JonSkeet, BTW, apakah Anda memperhatikan saya menggunakan konvensi yang sama untuk cuplikan kode yang Anda gunakan di C# in Depth? :-) Saya suka bukunya - terima kasih!   -  person Shaul Behr    schedule 29.08.2013
comment
Saya belum melakukannya, tapi saya senang hal ini berhasil dilakukan. Sekarang setelah Anda menunjukkan kodenya, ini jauh lebih masuk akal - karena sekarang Anda secara efektif mendapatkan pohon ekspresi yang berisi panggilan ke Active, yang tidak sama dengan memanggil Active sendiri (seperti sebelumnya).   -  person Jon Skeet    schedule 29.08.2013
comment
@JonSkeet, memang sekarang lebih masuk akal, tetapi apakah Anda punya solusinya? :-)   -  person Shaul Behr    schedule 29.08.2013
comment
Sayangnya bukan tanpa menulis ulang pohon ekspresi :(   -  person Jon Skeet    schedule 29.08.2013


Jawaban (2)


Anda memiliki dua masalah yang tidak berhubungan dalam kode. Yang pertama adalah Entity Framework tidak dapat menangani ekspresi yang dilemparkan, seperti yang dilakukan metode ekstensi Anda.

Solusi untuk masalah ini adalah dengan menambahkan batasan class pada metode ekstensi Anda. Jika Anda tidak menambahkan batasan tersebut, ekspresi akan dikompilasi untuk menyertakan cast:

.Where (t => !((ISoftDeletable)t.IsDeleted))

Pemeran di atas membingungkan Entity Framework, itulah sebabnya Anda mendapatkan kesalahan runtime.

Ketika pembatasan ditambahkan, ekspresi menjadi akses properti sederhana:

 .Where (t => !(t.IsDeleted))

Ekspresi ini dapat diuraikan dengan baik dengan kerangka entitas.

Masalah kedua adalah Anda tidak bisa menerapkan metode ekstensi yang ditentukan pengguna dalam sintaksis kueri, namun Anda bisa menggunakannya dalam sintaksis Lancar:

db.ThingContainers.SelectMany(tc => tc.Things).Active()
    .Any(t => t.SatisfiesOtherCondition); // this works

Untuk melihat masalahnya, kita harus melihat kueri sebenarnya yang dihasilkan:

 db.ThingContainers
       .Where(tc => tc.Things.Active().Any(t => t.StatisfiesOtherCondition))
       .Select(tc => new { ... });

Panggilan Active() tidak pernah dieksekusi, tetapi dihasilkan sebagai ekspresi untuk diurai oleh EF. Benar saja, EF tidak tahu apa yang harus dilakukan dengan fungsi seperti itu, jadi EF memberikan dana talangan.

Solusi yang jelas (walaupun tidak selalu memungkinkan) adalah memulai kueri pada Things, bukan ThingContainers:

db.Things.Active().SelectMany(t => t.Container);

Solusi lain yang mungkin adalah dengan menggunakan Model Defined Functions, namun ini merupakan proses yang lebih rumit. Lihat ini, ini dan ini artikel MSDN untuk informasi lebih lanjut.

person felipe    schedule 29.08.2013
comment
Itu memang membantu saya dalam hal terkait, jadi beri +1 untuk itu, terima kasih! - person Shaul Behr; 29.08.2013
comment
@Shaul, saya telah memperluas jawabannya. Pada dasarnya, mencampurkan sintaks kueri dengan metode ekstensi adalah hal yang merepotkan. - person felipe; 30.08.2013
comment
Bagus sekali, tapi apa yang akan Anda lakukan jika ThingContainer juga ISoftDeletable, dan saya ingin menerapkan .Active() pada .Active() juga? :-) - person Shaul Behr; 30.08.2013
comment
db.Things.Active().SelectMany(t => t.Container).Active() ;). Ini tentu saja bisa sampai pada titik di mana Anda harus mengulangi ekspresi tersebut, jika kuerinya cukup rumit. - person felipe; 30.08.2013
comment
Lihatlah jawaban saya untuk apa yang saya lakukan pada akhirnya. Terima kasih atas bantuan Anda! - person Shaul Behr; 30.08.2013

Meskipun @felipe telah mendapatkan kredit jawaban, saya pikir saya juga akan memposting jawaban saya sendiri sebagai alternatif, meskipun serupa:

var query = from tc in db.ThingContainers.Active() // ThingContainer is also ISoftDeletable!
            join t in db.Things.Active() on tc.ID equals t.ThingContainerID into things
            where things.Any(t => t.SatisfiesOtherCondition)
            select new { ... };

Keuntungannya adalah menjaga struktur kueri kurang lebih sama, meskipun Anda kehilangan kelancaran hubungan implisit antara ThingContainer dan Thing. Dalam kasus saya, perdagangan berhasil menentukan hubungan secara eksplisit daripada harus menentukan kriteria Active() secara eksplisit.

person Shaul Behr    schedule 29.08.2013