Sunday, February 26, 2012

Is the simple stuff usually simple?

I got stuck on this earlier this morning.  I needed to take a break and come back to it later.  I did work it out, and it turned out to be really, really simple.  But it had me confused for a while.

The code below is c#.  I'm building a new website, using MVC3, entity framework 4.1 and code first.  I've renamed the classes for this example.

I've got a class, lets called it 'Whatever', within it, I've got a field of type 'Something', like so...

    public class Whatever
    {
        [ScaffoldColumn(false)]
        public int WhateverId { get; set; }
       
        [DisplayName("Whatever Name")]
        [Required(ErrorMessage = "A Whatever Name is required")]
        [StringLength(255)]
        public string Name { get; set; }

        [DisplayName("Something Else")]
        [Required(ErrorMessage = "You've forgotten something else?")]
        public SomethingElse OtherDetail { get; set; }

        public bool Deleted { get; set; }
    }

My SomethingElse class is a simple one too...

    public class SomethingElse
    {
        [ScaffoldColumn(false)]
        public int SomethingElseId { get; set; }

        [Required(ErrorMessage = "A Something Else Name is required")]
        [StringLength(16)]
        public string Name { get; set; }
    }

Using Code First with Entity Framework 4.1, I can populate my DB...

      SomethingElse somethingElse = SomethingElseDal.GetDefaultSomethingElse(context);
 
      Whatever whatever1 = new Whatever
      {
          Name = "blah blah 1",
          OtherDetail = somethingElse
      };

      Whatever whatever2 = new Whatever
      {
          Name = "blah blah 2",
          OtherDetail = somethingElse
      };

      using(WhateverDal whateverDal = new WhateverDal(context))
      {
          whateverDal.Add(whatever1);
          whateverDal.Add(whatever2);
      };

This drops it all nicely into my database.  I have three fields in my 'Whatever' table:
  • WhateverId
  • Name
  • OtherDetail_SomethingElseId
My problem comes when I try and read a 'Whatever' record from my DB.  Something like this...?

      AppDBContext db = new AppDBContext();

      Whatever default = (from w in db.Whatevers
                         select w).FirstOrDefault();

This extracts a Whatever record, but my OtherDetail property is null.  I could do a 2nd query to populate it, but that'd be piss poor.   So, whats the best way to populate the field as part of the same query?

A dumbass question, but it's something I haven't faced until now.

My first instinct was to code it up like this...

       Whatever default = db.Whatevers
           .Include("OtherDetails")
           .FirstOrDefault();

But it didn't work, it was complaining about 'OtherDetails' not existing.

I've used this before, but only when adding in rows from other tables, when there's no foreign key in the primary table.

ie.

Lets say my structure was like...

    public class Whatever
    {
        [ScaffoldColumn(false)]
        public int WhateverId { get; set; }
       
        [DisplayName("Whatever Name")]
        [Required(ErrorMessage = "A Whatever Name is required")]
        [StringLength(255)]
        public string Name { get; set; }

        public IList<SomethingElse> OtherDetails { get; set; }

        public bool Deleted { get; set; }
    }

And my SomethingElse looks like...

    public class SomethingElse
    {
        [ScaffoldColumn(false)]
        public int SomethingElseId { get; set; }

        public int WhatverId { get; set; }

        [Required(ErrorMessage = "A Something Else Name is required")]
        [StringLength(16)]
        public string Name { get; set; }
    }

Then I could do something like this...

        internal Whatever GetPopulatedWhatever(int id)
        {
            Whatever whatever = this._context.Whatevers
                .Include("OtherDetails")
                .Single(w => w.WhateverId == id);

            return whatever;
        }


Then it hit me!  I'd had the right idea - especially after querying my mate Gary, and he suggested a similar thing.  I had a typo in my code!

        internal Whatever GetPopulatedWhatever(int id)
        {
            Whatever whatever = this._context.Whatevers
                .Include("OtherDetail")
                .Single(w => w.WhateverId == id);

            return whatever;
        }



I had an extra 's' on the end of 'OtherDetail'.  Easy!

Off the back of this, there's probably an extra bit that needs sharing...

Let's say my 'Whatever' belongs as a list of whatever's to a parent class.

    public class Parent
    {
        [ScaffoldColumn(false)]
        public int ParentId { get; set; }
       
        [DisplayName("Parent Name")]
        [Required(ErrorMessage = "A Parent Name is required")]
        [StringLength(255)]
        public string Name { get; set; }

        public IList<Whatever> Whatevers { get; set; }

        public bool Deleted { get; set; }
    }

    public class Whatever
    {
        [ScaffoldColumn(false)]
        public int WhateverId { get; set; }

        public int ParentId { get; set; }
       
        [DisplayName("Whatever Name")]
        [Required(ErrorMessage = "A Whatever Name is required")]
        [StringLength(255)]
        public string Name { get; set; }

        [DisplayName("Something Else")]
        [Required(ErrorMessage = "You've forgotten something else?")]
        public SomethingElse OtherDetail { get; set; }

        public bool Deleted { get; set; }
    }


I have a method that returns me my fully populated Parent class:

        internal Parent GetPopulatedParent(int id)
        {
            Parent parent = this._context.Parents
                .Include("Whatevers")
                .Single(p => p.ParentId == id);

            return parent;
        }


Even though this gives me my parent object, and a list of populated whatevers, what it hasn't done is populated the 'OtherDetail' field within each 'Whatever'.  To do that,  I need to make a slight amendment and add another Include:

        internal Parent GetPopulatedParent(int id)
        {
            Parent parent = this._context.Parents
                .Include("Whatevers")
                .Include("Whatevers.OtherDetail")
                .Single(p => p.ParentId == id);

            return parent;
        }


Nice!  :-)



No comments:

Post a Comment