So I am working on a project that contains a centralized communications page that contains all the “notes” from throughout the site. Right now I am able to bring the notes in on a per-model basis, but I am stuck with three groups that require not just the note, but also needs to be divided by parent so that each group of notes are allocated to the proper parent.
As a quick breakdown:
The top level is that of companies. Several sections within the company can have their own notes (multiple notes, so separate notes table for each section), and so there are several notes tables brought in from this level.
The next level is a “cycle” that the company can go through. Only the most recent cycle will ever get dealt with, so this ends up being rather simple. This level contains the most sections with separate notes tables, but by having the current cycle contained in a claim, I can filter for just the current cycle.
The lowest level involves a few tables that hang off of the cycle. Since these are not as limited as the cycle, I need to be able to bring in not just these tables, but also the notes for every entry in those tables. While the tables can be filtered by the Cycle claim, the notes tables don’t have that information.
My problem is with this lowest level. I do not know how to create a “multi-IEnumerable” such that both parent and child data can be attached to a single viewModel container. This is preventing me from creating a Linq Lambda expression that can fill that container. Once I have the container filled, the rest ought to be easy -- I just create a pair of nested for loops that extract first the parent (just the name of the table row) and thet the child (all the notes entries from the notes table for that table row).
In other words, I need a left join to fill a single viewmodel, and I cannot do this until I find out how to create an IEnumerable that holds both models.
Parent
+----+------+
| id | name |
+----+------+
| 1 | John |
| 2 | Mike |
-------------
Child
+----+-----+-----------------+------------+
| id | Pid | note | date |
+----+-----+-----------------+------------+
| 1 | 2 | Mike's notes | 2016-01-15 |
| 2 | 1 | John’s notes | 2016-02-22 |
| 3 | 2 | More Mike’s | 2016-02-24 |
| 4 | 2 | Yet more Mike’s | 2016-03-01 |
-------------------------------------------
Page output
John
2016-02-22 John’s Notes
[text field] [submit]
Mike
2016-01-15 Mike’s notes
2016-02-24 More Mike’s
2016-03-01 Yet more Mike’s
[text field] [submit]
Plus, please keep in mind that this is just one of many models being brought into the page, right now I have almost 20 on the same page, so my controller start out with var viewModel = new CommunicationsViewModel();
and has multiple models being brought in by attaching them to the viewModel
by way of IEnumerables in the Controller's model. My only problem is that I need to attach two models to one IEnumerable, so that my JOIN query can succeed in dumping content into a viewModel container.
Right now, my most promising Lambda expression is thus:
viewModel.Active = await db.Activity
.Join(
db.ActivityNotes.Include(y => y.NotesStatus),
act => act.ActivityId,
actnot => actnot.ActivityId,
(act, actnot) => new { Activity = act, ActivityNotes = actnot }
)
.Where(act => act.CycleId == cycleId)
.ToListAsync();
But of course I cannot do anything with it until I figure out how to combine both Activity and ActivityNotes into a single IEnumerable called Active.
Edit: For Erik Philips:
For my communications page, I have the following model laid out:
public class CommunicationsViewModel {
...more IEnumerated models...
public IEnumerable<Activity> Active { get; set; }
public IEnumerable<ActivityNotes> ActiveComm { get; set; }
}
Notice the last two entries, parent and child. The parent needs to be filtered for a cycle Guid, but that's it.
My OnModelCreating:
#region Activity
modelBuilder.Entity<Activity>().HasMany(x => x.ActivityNotes).WithRequired(x => x.Activity).HasForeignKey(x => x.ActivityId).WillCascadeOnDelete(true);
modelBuilder.Entity<Activity>().HasKey(x => x.ActivityId);
modelBuilder.Entity<Activity>().Property(x => x.ActivityId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Activity>().Property(x => x.CycleId).IsRequired();
modelBuilder.Entity<Activity>().Property(x => x.ActivityName).HasColumnType("nvarchar").HasMaxLength(50).IsOptional();
modelBuilder.Entity<Activity>().Property(x => x.ActivityDate).HasColumnType("date").IsRequired();
... redundant content removed ...
#endregion
#region ActivityNotes
modelBuilder.Entity<ActivityNotes>().HasKey(x => x.ActivityNotesId);
modelBuilder.Entity<ActivityNotes>().Property(x => x.ActivityNotesId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<ChampionNotes>().Property(x => x.Notes).IsRequired();
#endregion
My Index, highly abridged to contain only the relevant piece:
public async Task<ActionResult> Index() {
var cycleId = new Guid(User.GetClaimValue("CWD-Cycle"));
var viewModel = new CommunicationsViewModel();
viewModel.Active = await db.Activity.Where(x => x.CycleId == cycleId).Where(x => x.Active == true).ToListAsync();
...more viewModelled content brought in...
return View(viewModel);
}
And there is the content of my view:
@foreach(var item in Model.Active) {
@Html.DisplayFor(modelItem => item.ActivityName)
foreach(var subItem in Model.ActiveComm) {
@Html.DisplayFor(modelItem => subItem.Notes)
}
}
…Aaaand it basically fails at the second foreach
, the classic “Object reference not set to an instance of an object”. Which is catchall for ASP.NET saying “whoops, you dun goofed; good luck finding out where, tho.”