Bagaimana menyiasati masalah Circular Reference dengan JSON dan Entity

13

Saya telah bereksperimen dengan membuat situs web yang memanfaatkan MVC dengan JSON untuk lapisan presentasi saya dan kerangka kerja Entity untuk model data / database. Masalah saya mulai berperan dengan membuat serialisasi objek Model saya ke dalam JSON.

Saya menggunakan metode kode pertama untuk membuat database saya. Ketika melakukan kode metode pertama hubungan satu ke banyak (orang tua / anak) mengharuskan anak untuk memiliki referensi kembali ke orang tua. (Contoh kode saya menjadi salah ketik tetapi Anda mendapatkan gambar)

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

Ketika mengembalikan objek "induk" melalui JsonResult, kesalahan referensi melingkar dilemparkan karena "child" memiliki properti induk kelas.

Saya telah mencoba atribut ScriptIgnore tetapi saya kehilangan kemampuan untuk melihat objek anak. Saya perlu menampilkan informasi dalam tampilan anak induk di beberapa titik.

Saya telah mencoba membuat kelas dasar untuk orang tua dan anak yang tidak memiliki referensi melingkar. Sayangnya ketika saya mencoba mengirim baseParent dan baseChild, ini dibaca oleh JSON Parser sebagai kelas turunannya (saya cukup yakin konsep ini lolos dari saya).

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

Satu solusi yang saya buat adalah membuat Model "View". Saya membuat versi sederhana dari model database yang tidak menyertakan referensi ke kelas induk. Model tampilan ini masing-masing memiliki metode untuk mengembalikan Versi Database dan konstruktor yang menggunakan model database sebagai parameter (viewmodel.name = databasemodel.name). Metode ini tampaknya dipaksakan meskipun berhasil.

CATATAN: Saya memposting di sini karena saya pikir ini diskusi yang lebih layak. Saya bisa memanfaatkan pola desain yang berbeda untuk mengatasi masalah ini atau itu bisa sesederhana menggunakan atribut yang berbeda pada model saya. Dalam pencarian saya, saya belum melihat metode yang baik untuk mengatasi masalah ini.

Tujuan akhir saya adalah memiliki aplikasi MVC yang bagus yang memanfaatkan JSON untuk berkomunikasi dengan server dan menampilkan data. Sambil mempertahankan model yang konsisten di seluruh lapisan (atau yang terbaik yang bisa saya buat dengan).

DanScan
sumber

Jawaban:

6

Saya melihat dua subjek berbeda dalam pertanyaan Anda:

  • Bagaimana mengelola referensi sirkuler ketika membuat serial ke JSON?
  • Seberapa aman menggunakan entitas EF sebagai entitas model dalam pandangan Anda?

Mengenai referensi melingkar, saya minta maaf untuk mengatakan bahwa tidak ada solusi sederhana. Pertama karena JSON tidak dapat digunakan untuk mewakili referensi melingkar, kode berikut:

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

Hasil dalam: TypeError: Converting circular structure to JSON

Satu-satunya pilihan yang Anda miliki adalah menjaga hanya komponen komposit -> bagian dari komposisi dan membuang komponen "navigasi belakang" -> komposit, sehingga dalam contoh Anda:

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

Tidak ada yang mencegah Anda menyusun ulang properti navigasi ini di sisi klien Anda, di sini menggunakan jQuery:

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

Tetapi Anda harus membuangnya lagi sebelum mengirimnya kembali ke server, karena JSON.stringify tidak akan dapat membuat serialisasi referensi melingkar:

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

Sekarang ada masalah menggunakan entitas EF sebagai entitas model tampilan Anda.

EF pertama cenderung menggunakan Proxy Dinamis dari kelas Anda untuk menerapkan perilaku seperti deteksi perubahan atau pemuatan malas, Anda harus menonaktifkannya jika Anda ingin membuat serial entitas EF.

Selain itu, menggunakan entitas EF di UI dapat berisiko karena semua binder default akan memetakan setiap bidang dari bidang permintaan ke entitas termasuk yang Anda tidak ingin disetel oleh pengguna.

Jadi, jika Anda ingin aplikasi MVC Anda dirancang dengan baik, saya akan merekomendasikan untuk menggunakan model tampilan khusus untuk mencegah "nyali" model bisnis internal Anda agar tidak terpapar ke klien, jadi saya akan merekomendasikan Anda model tampilan tertentu.

Julien Ch.
sumber
Apakah ada cara mewah dengan teknik berorientasi objek yang bisa saya dapatkan baik dari referensi melingkar dan masalah EF.
DanScan
Apakah ada cara mewah dengan teknik berorientasi objek yang bisa saya dapatkan baik dengan referensi melingkar dan masalah EF? Seperti BaseObject diwarisi oleh entitasObject dan oleh viewObject. Jadi entitasObject akan memiliki referensi melingkar tetapi viewObject tidak akan memiliki referensi melingkar. Saya telah menyelesaikan ini dengan membangun viewObject dari entityObject (viewObject.name = entityObject.name) tetapi ini tampaknya hanya buang-buang waktu. Bagaimana saya bisa mengatasi masalah ini?
DanScan
Mereka Anda sangat banyak. Penjelasan Anda sangat jelas dan mudah dimengerti.
Nick
2

Alternatif yang lebih sederhana untuk mencoba membuat serialisasi objek adalah dengan menonaktifkan serialisasi objek induk / anak. Sebagai gantinya, Anda dapat membuat panggilan terpisah untuk mengambil objek induk / anak terkait saat dan saat Anda membutuhkannya. Ini mungkin tidak ideal untuk aplikasi Anda, tetapi ini pilihan.

Untuk melakukan ini, Anda bisa mengatur DataContractSerializer dan mengatur properti DataContractSerializer.PreserveObjectReferences menjadi 'false' di konstruktor kelas model data Anda. Ini menentukan bahwa referensi objek tidak boleh dipertahankan pada serialisasi tanggapan HTTP.

Contoh:

Format json:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.None;

Format XML:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);

Ini berarti bahwa jika Anda mengambil item yang memiliki objek anak dirujuk, objek anak tidak akan diserialisasi.

Lihat juga kelas DataContractsSerializer .

Ciaran Gallagher
sumber
1

Serializer JSON yang berkaitan dengan Referensi Lingkaran

Berikut adalah contoh kebiasaan Jackson JSONSerializeryang menangani referensi melingkar dengan membuat serial kejadian pertama dan menyimpan * referenceke kejadian pertama pada semua kejadian berikutnya.

Berurusan dengan Referensi Lingkaran ketika Menerialisasi objek dengan Jackson

Cuplikan parsial yang relevan dari artikel di atas:

private final Set<ObjectName> seen;

/**
 * Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
 * @param on ObjectName to serialize
 * @param jgen JsonGenerator to build the output
 * @param provider SerializerProvider
 * @throws IOException
 * @throws JsonProcessingException
 */
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
    if (this.seen.contains(on))
    {
        jgen.writeString(on.toString());
    }
    else
    {
        this.seen.add(on);
        jgen.writeStartObject();
        final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
        for (final MBeanAttributeInfo ai : ais)
        {
            final Object attribute = this.getAttribute(on, ai.getName());
            jgen.writeObjectField(ai.getName(), attribute);
        }
        jgen.writeEndObject();
    }
}

sumber
0

Satu solusi yang saya buat adalah membuat Model "View". Saya membuat versi sederhana dari model database yang tidak menyertakan referensi ke kelas induk. Model tampilan ini masing-masing memiliki metode untuk mengembalikan Versi Database dan konstruktor yang menggunakan model database sebagai parameter (viewmodel.name = databasemodel.name). Metode ini tampaknya dipaksakan meskipun berhasil.

Mengirimkan minimal data adalah satu-satunya jawaban yang benar. Ketika Anda mengirim data dari database, biasanya tidak masuk akal untuk mengirimkan setiap kolom dengan semua asosiasi. Konsumen tidak perlu berurusan dengan asosiasi dan struktur basis data, yaitu untuk basis data. Ini tidak hanya akan menghemat bandwidth tetapi juga lebih mudah untuk mempertahankan, membaca, dan mengkonsumsi. Permintaan data dan kemudian modelkan untuk apa yang sebenarnya Anda butuhkan untuk mengirim. minimum.

Dante
sumber
Diperlukan lebih banyak waktu pemrosesan ketika Anda berbicara tentang data besar, karena sekarang Anda harus mengubah semuanya dua kali.
David van Dugteren
-2

.Include(x => x.TableName ) tidak mengembalikan hubungan (dari tabel utama ke tabel dependen), atau hanya mengembalikan satu baris data, FIX HERE:

/programming/43127957/include-not-working-in-net-core-returns-one-parent

Juga, di Startup.cs pastikan Anda memilikinya di atas:

using Microsoft.EntityFrameworkCore; 
using Newtonsoft.Json; 
using Project_Name_Here.Models;
joe hoeller
sumber
anak wat? erm .. apa?
amel