2

Imagine these three EF classes

    class Instrument
    {
        public long InstrumentId { get; set; }
        public string Model { get; set; }

        public dynamic AsJson()
        {
            return new  
            {
               instrumentId = this.InstrumentId,
               model = this.Model
            }
        }
    }

    class Musician
    {
        public long MusicianId { get; set; }
        public virtual Instrument Instrument { get; set; }  // notice navigation
        public string Name { get; set; } 

        public dynamic AsJson()
        {
            return new  
            {
               musicianId = this.MusicianId,
               name = this.Name,
               instrument = this.Instrument.AsJson()
            }
        }
    }

    class MusicBand
    {
        public long MusicBandId { get; set; }
        public string Name { get; set; } 
        public virtual List<Musician> Members { get; set; }
    }



Imagine now that we need multiple actions, all similar with each other, that return JSON.
lets call this Approach (A)

    // ajax/Bands/Members
    //
    public JsonResult Members(long musicBandId)
    {
        MusicBand g = db.MusicBands.SingleOrDefault(g => g.MusicBandId == musicBandId);

        if (g == null)
             return null;

        return Json(new
        {
            error = false,
            message = "",
            members = from p in g.Members.ToList() select p.AsJson()

        }, JsonRequestBehavior.AllowGet);
    }

The problem with it is that the ToList() is required for the methods AsJson() to be used... so there's a considerable amount of work done in memory


In the following approach, lets call it Approach (B), this is not a problem.. the work done in memory is the minimum, and most of the work is done in SQL. In fact, it is one large SQL query that contains everything needed..

    // ajax/Bands/Members
    //
    public JsonResult Members(long musicBandId)
    {
        MusicBand g = db.MusicBands.SingleOrDefault(g => g.MusicBandId == musicBandId);

        if (g == null)
             return null;

        return Json(new
        {
            error = false,
            message = "",
            persons = from p in g.Members select new
                      {
                          musicianId = p.MusicianId,
                          name = p.Name,
                          instrument = select new 
                                       {
                                          instrumentId = instrument.InstrumentId,
                                          model = instrument.Model
                                       }
                      }

        }, JsonRequestBehavior.AllowGet);
    }




Approach A.
Pros: Neat code, I can reuse code in other actions, less coupling
Cons: performance issues (work done in memory!)

Approach B:
Pros: Uggly code, if other action needs something similar, I'll end up copy pasting code! which brings coupling (modify similar code multiple times)
Cons: No performance issues (work done in SQL!)

Finally, the question: Im looking for another approach (C), that has the better of both (A) and (B), Reutilization and no performance issues

I'd like to hear how big systems, with many navigation properties, achieve this.
How they manage to reduce coupling when different JSONS (that share subparts) are required

Also, a sub-question:
In approach (A), will the following make a difference?

db.MusicBands.Include(g => g.Members.Select(m => m.Instrument)).SingleOrDefault(g => g.MusicBandId == musicBandId)

or it wont? (keeping the rest of the code with no change)

1 Answer 1

2

The starting point of your question is these AsJson() methods in the entity classes. But that's something I wouldn't do in the first place. Firstly, it introduces a transport concept in your domain and secondly, the representation of an entity may depend on the use case: maybe in other cases you only want to show musicians without instruments.

If you want to serialize entities as JSON you generally want to disable proxy generation, which disables lazy loading and materializes the original entity types...

db.ProxyCreationEnabled = false;

...and eagerly load everything you want to return...

MusicBand g = db.MusicBands
                .Include(mb => mb.Members.Select(m => m.Instrument))
                .SingleOrDefault(mb => mb.MusicBandId == musicBandId);

...and return g as JSON.

This does the job in one SQL query. In your alternatives there will always be at least two queries, because you first fetch the MusicBand and then the members (and instruments) by lazy loading.

If you want to serialize other representations of entities you should map then to DTO (or view model) objects. Here a tool like AutoMapper comes in handy.

Sign up to request clarification or add additional context in comments.

6 Comments

You are focusing more on the performance issue I have, which is 50% of what I'm looking. The other 50% is reutilization and that's where I'm stuck. How will these DTOs help me achieve that if I need to fill them in every different query I do?
With AutoMapper this could be a simple statement like Mapper.Map<MusicBandDto>(g). You can tack that on an individual query, but of course you can also have a service that exposes queries and that you map to different DTOs e.g. in controllers.
AutoMapper can also project an IQueryable directly to a list of DTO objects in one short statement.
I will take a look at AutoMapper and mark your answer as the correct one if that (AutoMapper) is what solves the other half of the problem
AM will also map nested collections if the collection names in the DTOs have the same names (or else you can configure source and target collections). It really is the tool to get rid of all these nested select new {} statements.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.