0

I am trying to figure out the best way to ensure that a List of objects within another object are always ordered the same way.

I currently have my class Jobsite:

public class Jobsite
{
    #region Class Properties
    [Key]
    public long Id { get; set; }
    ...

    private DateTime? _createdOn;
    public DateTime CreatedOn
    {
        get
        {
            if (_createdOn.HasValue)
                return _createdOn.Value;
            else
                return DateTime.UtcNow;
        }
        set { _createdOn = value; }
    }

    public long? CreatedById { get; set; }
    #endregion


    #region Navigation Properties
    [ForeignKey("CustomerId")]
    public Customer Customer { get; set; }

    public List<Job> Jobs { get; set; }

    public List<JobsiteNote> JobsiteNotes { get; set; }
    #endregion
}

This class is being populated by Entity Framework like so:

public class EFJobsiteRepository : IJobsiteRepository
{
    private EFDbContext _context = new EFDbContext();

    public IQueryable<Jobsite> Jobsites
    {
        get
        {
            return _context.Jobsite
                .Include("JobsiteNotes.CreatedBy")
                .Include("Customer")
                .Include("Jobs.JobType");
        }
    }

    public void Save(Jobsite jobsite)
    {
        if (jobsite.Id == 0)
        {
            _context.Jobsite.Add(jobsite);
        }
        else
        {
            _context.Entry(jobsite).State = EntityState.Modified;
        }

        _context.SaveChanges();
    }
}

Pretty standard stuff. Everything works fine. In my view, I want to list all the "JobsiteNotes", however I want them to be ordered decending

So right now, I have in my view:

<div>
    @foreach (var note in Model.Jobsite.JobsiteNotes.OrderByDescending(x => x.CreatedOn))
    {
        @Html.Partial("Note", note)
    }
</div>

This works fine, however I think ideally, this sorting should be done in the getter/setter of that list. This way, I don't have to explicitly order the list of notes in every view I want to use them.

I figured I could just do something like this:

private List<JobsiteNote> _jobsiteNotes;
    public List<JobsiteNote> JobsiteNotes 
    { 
        get
        {
            return _jobsiteNotes.OrderByDescending(x => x.CreatedOn).ToList();
        }
        set
        {
            _jobsiteNotes = value;
        }
    }

However, the list is empty... This is probably something silly, but how come I can't add the Order logic here?

EDIT The Jobsite class is being initialized in the controller. I'm using the Ninject library for dependency injection.

This code below is what populates the _jobsiteNotes List in the Jobsite class. EF does this all for me. When I get the jobsite from the repository, it brings it back with all the notes associated with it.

public class JobsiteController : Controller
{
    private IJobsiteRepository _jobsiteRepository;

    public JobsiteController(IJobsiteRepository jobsiteRepo)
    {
        _jobsiteRepository = jobsiteRepo;
    }

    // GET: Jobsite
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Manage(long id)
    {
        var jobsite = _jobsiteRepository.Jobsites.FirstOrDefault(x => x.Id == id);

        var model = new JobsiteViewModel
        {
            ActiveJobId = jobsite.Jobs.FirstOrDefault().Id,
            Jobsite = jobsite
        };

        return View(model);
    }
}

EDIT 2 I found this SO question: How to include sorted navigation properties with Entity Framework

I am trying to order the navigation property. However, that solution doesn't seem like the best way to do this.

When Entity Framework is populating the JobsiteNotes, it doesn't use the setter at all. I put a breakpoint right after this line in my controller:

var jobsite = _jobsiteRepository.Jobsites.FirstOrDefault(x => x.Id == id);

And all the notes get populated into the list, however it never hits the setter.

Community
  • 1
  • 1
Nick Young
  • 885
  • 1
  • 10
  • 21
  • can you show the code initializing `_jobsiteNotes`? – DJ. May 01 '15 at 18:07
  • this pattern is bad, you shouldn't initialize your context every time you create an object, and you aren't disposing it. you should only create a context when you need it, and always wrap in a `using()` – DLeh May 01 '15 at 18:21
  • I stole this pattern right out of the book "Pro ASP.NET MVC 4" by Adam Freeman. If I wrap the context in a using statement, the context gets disposed of, and DI doesn't work properly? What pattern would you suggest using? – Nick Young May 01 '15 at 18:32
  • I still do not see where you set the value to `__jobsiteNotes`, is it on the ViewModel object? – DJ. May 01 '15 at 18:51
  • Your getter/setter method will work. You should reorder it so you are setting the order in the setter, this way the getter doesn't have to do it every time it is referenced. As far as it being null, that's got to be another issue. Have you stepped through your code to verify that `value` in your setter is populated? – Jacob Roberts May 01 '15 at 18:53
  • The JobsiteNotes is a navigation property. Entity Framework somehow populates this without ever using the setter. I put a breakpoint there and it never gets hit, however if I just use the default {get;set;} the list does get populated. – Nick Young May 01 '15 at 21:56

3 Answers3

0

do the sorting in JobSite class:

public class Jobsite
{
   public Jobsite(){
       if(this.JobSiteNotes.Any())
            the.JobSiteNotes = this.JobSiteNotes.OrderBy(..);
   }

actually , I think this would work and even simplier

   public Jobsite(){
       if(this.JobSiteNotes.Any())
            the.JobSiteNotes.OrderBy(...);
   }
Scott Selby
  • 9,420
  • 12
  • 57
  • 96
  • The list is always empty when this class gets instantiated. I'm not sure on the exact process of how Entity Framework populates the class, but the constructor is executed prior to anything being populated. – Nick Young May 01 '15 at 21:54
  • no - with entities this will work - unless you use virtual keywords and lazy loading which was not the case here – Scott Selby May 01 '15 at 22:41
0

You've already got a JobSiteViewModel wrapping the JobSite. The ViewModel pattern exists to separate view-related concerns from data-provider-related concerns, so take advantage of that.

I suggest that the correct place for an ordered collection of JobSiteNotes is in your ViewModel, like so:

public List<JobsiteNote> MostRecentNotes 
{ 
    get { return Jobsite.JobsiteNotes.OrderByDescending(x => x.CreatedOn)); } 
}

And you'd expose that in your View as:

@foreach (var note in Model.MostRecentNotes)
{
    @Html.Partial("Note", note)
}

This way, neither your EF-aware class nor your View is responsible for sorting the notes; they get sorted at the point that your View accesses the ViewModel's property.

Dan J
  • 16,319
  • 7
  • 50
  • 82
  • Thank you, I think this way makes the most sense. I find it hard sometimes to wrap my head around this MVC pattern coming from web forms. – Nick Young May 05 '15 at 17:20
-1

You should be taking the JobsiteNotes from the already JobSite fetched object.

private List<JobsiteNote> _jobsiteNotes = null;
public List<JobsiteNote> JobsiteNotes 
{ 
    get
    {
         if (_jobsiteNotes == null)
          {
            _jobsiteNotes = this.Jobsite.JobsiteNotes.OrderByDescending(x => x.CreatedOn).ToList();
          }

          return _jobsiteNotes;
    }
}

Then in your view you would use this as:

<div>
    @foreach (var note in Model.JobsiteNotes)
    {
        @Html.Partial("Note", note)
    }
</div>
DJ.
  • 528
  • 7
  • 16
  • This throws a recursion or infinite loop exception?? – Nick Young May 01 '15 at 21:52
  • Yeah, it's referencing the property in its own getter. That's not going to work. :) – Dan J May 01 '15 at 22:03
  • `Jobsite` in your viewmodel isn't supossed to refer to the object entity initialize in the `Manage` action? The code suggested is supposed to go in your ViewModel! – DJ. May 04 '15 at 15:31