How to render custom HTML in Nonfactors/MVC6 grid - asp.net-core

Since moving on from .net to .Net Core, Ive had to look for a WebGrid Replacement, which lead me to NonFactors MVC6 grid. I have gotten all the basic understanding of how all if works, now I am trying to understand how to use "#helper" function to display reusable HTML in the table.
Previously in .Net #helper allowed for defining customer HTML then using it in WebGrid, like so The Helper Function and the webgrid Webgrid. Now im currently learning how to do the same functionality in Razor Pages, and im currently at a dead end.
What I would Like to do, is using the MVC6 grid (Grid) with this custom checkbox (HTML)

When using the MVC6 Grid to add column, you could use the RenderedAs() and Html.Raw() method to add the checkbox. Code like this:
Model:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string MaritalStatus { get; set; }
public int Age { get; set; }
public DateTime Birthday { get; set; }
public bool IsWorking { get; set; }
public bool IsSelected { get; set; }
}
public class PersonViewModel
{
public List<Person> Persons { get; set; }
public List<string> SelectedName { get; set; }
}
Controller:
public IActionResult Index3()
{
//Initial data.
var personvm = new PersonViewModel()
{
Persons = new List<Person>()
{
new Person() { Id = 1, Name = "Joe", Surname = "Crosswave", MaritalStatus = "Married", Age = 32, Birthday = DateTime.Now, IsWorking = false },
new Person() { Id = 2, Name = "Merry", Surname = "Lisel", MaritalStatus = "Widowed", Age = 42, Birthday = DateTime.Now, IsWorking = false },
new Person() { Id = 3, Name = "Henry", Surname = "Crux", MaritalStatus = "Single", Age = 29, Birthday = DateTime.Now, IsWorking = true },
new Person() { Id = 4, Name = "Cody", Surname = "Jurut", MaritalStatus = "", Age = 49, Birthday = DateTime.Now, IsWorking = false },
new Person() { Id = 5, Name = "Simon", Surname = "Scranton", MaritalStatus = "Single", Age = 34, Birthday = DateTime.Now, IsWorking = false },
new Person() { Id = 6, Name = "Leena", Surname = "Laurent", MaritalStatus = "Divorced", Age = 19, Birthday = DateTime.Now, IsWorking = false },
new Person() { Id = 7, Name = "Ode", Surname = "Cosmides", MaritalStatus = "Married", Age = 54, Birthday = DateTime.Now, IsWorking = true },
new Person() { Id = 8, Name = "Nicky", Surname = "Cassel", MaritalStatus = "Married", Age = 32, Birthday = DateTime.Now, IsWorking = true }
},
SelectedName = new List<string>() { "Merry", "Henry", "Leena", "Nicky" }
};
return View(personvm);
}
View: check the last column.
#model PersonViewModel
#(Html
.Grid(Model.Persons)
.Build(columns =>
{
columns.Add(model => Html.CheckBox("Check_" + model.Id)).Titled(Html.CheckBox("CheckAll"));
columns.Add().RenderedAs((model, row) => row + 1).Titled("#").Css("text-center");
columns.Add(model => model.Name).Titled("Name");
columns.Add(model => model.Surname).Titled("Surname");
columns.Add(model => model.MaritalStatus).Titled("Marital status");
columns.Add(model => model.Age).Titled("Age");
columns.Add(model => model.Birthday).Titled("Birthday").Formatted("{0:d}");
columns.Add(model => model.IsWorking).Titled("Employed").RenderedAs(model => model.IsWorking == true ? "Employed" : "Unemployed");
columns.Add(model => model.IsSelected).Titled("Selected")
.RenderedAs(model => Model.SelectedName.Contains(model.Name) ?
Html.Raw("<input type='checkbox' name='Input.SelectedAccessRightsIds' value='"+ model.Id + "' checked />")
: Html.Raw("<input type='checkbox' name='Input.SelectedAccessRightsIds' value='" + model.Id + "' />"));
})
)
The output as below:
Reference: MVC6 Grid Formatting
[Note] Before using MVC6 Grid, please make sure you have installed it.

Related

OptGroup in SelectListItem Not showing in DropDownList

I wrote a method to show Items and in Groups in DropDownlist with SelectListItem, But the problem is that only show the first group name and child plus the childs of other groups. The problem is that do not show second, third,.. groups (but show their childs).
My model is
public class PermissionsViewModel
{
public long ID { get; set; }
public string Title { get; set; }
public long TypeId { get; set; }
public long? ParentId { get; set; }
public string ParentTitle { get; set; }
public List<PermissionsViewModel> ParentList { get; set; }
public List<PermissionsViewModel> OperationsList { get; set; }
public List<PermissionTypesDto> PermissionTypesList { get; set; }
public bool Status { get; set; }
}
Method to retrieve data:
public Dictionary<long?,List<PermissionsViewModel>> GetPermissionsByModule()
{
var ItemValue = (_ipermissionTypes.Expose().FirstOrDefault(x => x.Title == "Operation").Id);
var permissionbymodule = _ntumcontext.Tbl_Permissions
.Where(x => x.Status == true && x.TypeId == ItemValue)
.Select(x => new PermissionsViewModel
{
ID = x.ID,
Title = x.Title,
Status = x.Status,
ParentId = x.ParentId,
ParentTitle=x.permission.Title,
TypeId=x.TypeId,
}).AsEnumerable().GroupBy(x => x.ParentId).ToList();
return permissionbymodule.ToDictionary(k => k.Key, v => v.ToList());
}
And the Method to get on razor page (View):
public List<SelectListItem> Permissions = new List<SelectListItem>();
public List<SelectListItem> GetPermissionsByModule()
{
var AllPermissions = _ipermissionsApplication.GetPermissionsByModule();
foreach (var (key, value) in AllPermissions)
{
var parentTitle = _ipermissionsApplication.GetDetails(key).Title; //get group title from key
var group = new SelectListGroup() { Name = parentTitle };
foreach (var per in value)
{
var item = new SelectListItem(per.Title, per.ID.ToString())
{
Group = group
};
Permissions.Add(item);
}
}
return Permissions;
}
And in cshtml :
<select asp-for="RoleVM.SelectedPermissions" asp-items="Model.Permissions">
At present with the above codes, the problem is that do not show the second and third and ... , only show the first group name, but show all child items of all groups.
Firstly,you need to check the value of AllPermissions,maybe the parentID is not exists,and then you need to check var parentTitle = _ipermissionsApplication.GetDetails(key).Title;maybe the second and third and.. parentTitle is null.
Here is a working demo(I use fake data):
public class TestPermissionsModel : PageModel
{
public List<SelectListItem> Permissions = new List<SelectListItem>();
public void GetPermissionsByModule()
{
var AllPermissions = new Dictionary<long, List<PermissionsViewModel>>()
{
{1,new List<PermissionsViewModel>{ new PermissionsViewModel{ ID=11,Title="title11", ParentId=1}, new PermissionsViewModel { ID = 12, Title = "title12", ParentId = 1 } } },
{2,new List<PermissionsViewModel>{ new PermissionsViewModel{ ID=21,Title="title21", ParentId=2}, new PermissionsViewModel { ID = 22, Title = "title22", ParentId = 2 } } },
{3,new List<PermissionsViewModel>{ new PermissionsViewModel{ ID=31,Title="title31", ParentId=3}, new PermissionsViewModel { ID = 32, Title = "title32", ParentId = 3 } } }
};
foreach (var (key, value) in AllPermissions)
{
var parentTitle = "parentTitle" + key; //get group title from key
var group = new SelectListGroup() { Name = parentTitle };
foreach (var per in value)
{
var item = new SelectListItem(per.Title, per.ID.ToString())
{
Group = group
};
Permissions.Add(item);
}
}
}
public void OnGet()
{
GetPermissionsByModule();
}
}
View:
<select asp-items="Model.Permissions"></select>
result:

Seed Many-To-Many Database in C# and EF Core

I would like to seed my Database with EF Core and seeding the connecting table for the many-to-many relationship doesn't work.
I've got three models:
public class Droid
{
public int DroidId { get; set; }
public string DroidName { get; set; }
}
public class Colors
{
public int ColorID { get; set; }
public string Color { get; set; }
}
public class DroidColors
{
public int DroidColorID { get; set; }
public int ColorID { get; set; }
public int DroidID { get; set; }
}
And I'm seeding the database right at the beginning. Seeding the droid table and the color table works fine, but the droidcolor table just stays emtpy.
using (var context = new DBContext(serviceProvider.GetRequiredService<DbContextOptions<DBContext>>()))
{
if (context.Colors.Any())
{
return;
}
var colors = new Color[]
{
new Color{Color = "White"},
new Parameter{Color = "Black" },
new Parameter{Color = "Orange"},
};
foreach (Color c in colors)
{
context.Colors.Add(c);
}
context.SaveChanges();
if (context.Droids.Any())
{
return;
}
var droids = new Droid[]
{
new Droid{DroidName = "R2-D2"},
new Droid{DroidName = "C-3PO"},
new Droid{DroidName = "BB-8"},
};
foreach (Droid d in droids)
{
context.Droids.Add(d);
}
context.SaveChanges();
if (context.DroidColors.Any())
{
return;
}
var droidcolors = new DroidColor[]
{
new DroidColor{DroidID = 1, ColorID = 1},
new DroidColor{DroidID = 1, ColorID = 2},
new DroidColor{DroidID = 2, ColorID = 1},
new DroidColor{DroidID = 2, ColorID = 2},
new DroidColor{DroidID = 2, ColorID = 3},
new DroidColor{DroidID = 3, ColorID = 1},
new DroidColor{DroidID = 3, ColorID = 3}
};
foreach (DroidColor DC in droidcolors)
{
context.Droids.Add(DC);
}
context.SaveChanges();
What am I missing? EF Core seems to ignore my third data array, after working with the first and the second as expected.

ASP.NET MVC & Entity Framework : adding a record to a table

I have created a table via EF code first migration called errands.
using System;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
namespace errandomWeb.Models
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<errands> errandList { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
}
This is further defined in my model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace errandomWeb.Models
{
public class errands
{
public int ID { get; set; }
public string UserID { get; set; }
public byte[] ProfilePicture { get; set; }
public string FirstName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Hometown { get; set; }
public string Rating { get; set; }
[Display(Name = "Posted")]
public DateTime DateTimePosted { get; set; }
public string LatitudePosted { get; set; }
public string LongitudePosted { get; set; }
public string LocationPosted { get; set; }
[Required(ErrorMessage = "Don't be shy and put some text, but keep it short.")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Don't be shy and put some text, but keep it short.")]
public string Title { get; set; }
[Required(ErrorMessage = "If you don't find your category, go with 'Other'")]
public string Category { get; set; }
[Required(ErrorMessage = "If you don't find your subcategory, go with 'Other'")]
public string SubCategory { get; set; }
[Required(ErrorMessage = "Come on.. people need to know that.")]
[StringLength(999, MinimumLength = 3, ErrorMessage = "Come on.. people need to know that.")]
public string Description { get; set; }
public byte[] Picture { get; set; }
[Required(ErrorMessage = "You don't have to give us your exact address, but people need to know where work needs to get done...")]
public string Location { get; set; }
[Display(Name = "As Soon As Possible")]
public bool ASAP { get; set; }
[Display(Name = "Start Date & Time")]
[Required(ErrorMessage = "When can the work be started?")]
public DateTime StartDateTime { get; set; }
[Required(ErrorMessage = "It's important to give a rough idea of how long this work will take.")]
[Display(Name = "Estimated Effort (h)")]
public string DurationInHours { get; set; }
[Required(ErrorMessage = "Until when will the work have to be done the latest?")]
[Display(Name = "End Date & Time")]
public DateTime EndDateTime { get; set; }
[Required(ErrorMessage = "That one has quite an impact on the amount.")]
public string Currency { get; set; }
[Required(ErrorMessage = "Do you think somebody would do the job for free?")]
public string Offering { get; set; }
public string errandFee { get; set; }
[Display(Name = "Total Amount")]
public string Price { get; set; }
public string errandTax { get; set; }
[Required(ErrorMessage = "Yup, we want to know that too.")]
[Display(Name = "Payment Method")]
public string PaymentMethod { get; set; }
public bool Published { get; set; }
}
}
Now what I am trying to do is to post 'errands', once posted, I would send them to a preview and from there I'd offer to edit (which is essentially going back to a similar view as composing the errand) or publish it, which would mean changing the Published bool from standard false to true.
I've accomplished that by just using the following controller code:
// GET: PostErrand
public ActionResult PostErrand()
{
var userID = User.Identity.GetUserId();
DateTime now = DateTime.Now.Date;
var model = new PostErrandViewModel
{
UserID = User.Identity.GetUserId(),
FirstName = UserManager.FindById(userID).FirstName,
Email = UserManager.FindById(userID).Email,
Phone = UserManager.FindById(userID).PhoneNumber,
Location = UserManager.FindById(userID).Hometown
//Rating =
};
ApplicationDbContext db = new ApplicationDbContext();
ViewBag.category = db.CategoryList.Select(m => new SelectListItem()
{
Text = m.Category,
Value = m.Category
}).ToList();
ViewBag.subcategory = db.SubCategoryList.Select(m => new SelectListItem()
{
Text = m.SubCategory,
Value = m.SubCategory
}).ToList();
ViewBag.currency = db.CurrencyList.Select(m => new SelectListItem()
{
Text = m.Currency,
Value = m.Currency
}).ToList();
ViewBag.paymentmethod = db.PaymentMethodList.Select(m => new SelectListItem()
{
Text = m.PaymentMethod,
Value = m.PaymentMethod
}).ToList();
var Loaded = new UserActivities
{
ActivityName = "PostErrand_Loaded",
ActivityTimeStamp = DateTime.Now.ToUniversalTime(),
UserID = User.Identity.GetUserId()
};
DB.UserActivityList.Add(Loaded);
DB.SaveChanges();
return View();
}
// POST: PostErrand
public ActionResult PostErrandToDB([Bind(Exclude = "Picture")]errands model)
{
string pictureFile = Request.Form["postErrandCroppedPicture"];
byte[] imageBytes = Convert.FromBase64String(pictureFile);
var userId = User.Identity.GetUserId();
var errand = new errands
{
UserID = User.Identity.GetUserId(),
FirstName = UserManager.FindById(userId).FirstName,
Email = UserManager.FindById(userId).Email,
Phone = UserManager.FindById(userId).PhoneNumber,
Hometown = UserManager.FindById(userId).Hometown,
Rating = model.Rating,
Category = model.Category,
SubCategory = model.SubCategory,
Title = model.Title,
Description = model.Description,
Location = model.Location,
ASAP = model.ASAP,
StartDateTime = model.StartDateTime,
DurationInHours = model.DurationInHours,
EndDateTime = model.EndDateTime,
DateTimePosted = DateTime.UtcNow.ToUniversalTime(),
Currency = model.Currency,
Offering = model.Offering,
Price = model.Price,
errandTax = model.errandTax,
PaymentMethod = model.PaymentMethod,
LatitudePosted = model.LatitudePosted,
LongitudePosted = model.LongitudePosted,
LocationPosted = model.LocationPosted,
Published = false
};
if (imageBytes.Length > 2)
{
errand.Picture = imageBytes;
}
DB.errandList.Add(errand);
DB.SaveChanges();
var Success = new UserActivities
{
ActivityName = "PostErrand_Success",
UserID = User.Identity.GetUserId(),
ActivityTimeStamp = DateTime.Now.ToUniversalTime(),
ActivityLatitude = model.LatitudePosted,
ActivityLongitude = model.LongitudePosted,
ActivityLocation = model.LocationPosted
};
DB.UserActivityList.Add(Success);
DB.SaveChanges();
return RedirectToAction("errandPreview");
}
// GET: errandPreview
public ActionResult errandPreview()
{
var userID = User.Identity.GetUserId();
var errand = DB.errandList.Where(x => x.UserID == userID)
.OrderByDescending(x => x.DateTimePosted)
.FirstOrDefault();
var model = new errands
{
ID = errand.ID,
Title = errand.Title,
Category = errand.Category,
SubCategory = errand.SubCategory,
Picture = errand.Picture,
Description = errand.Description,
Location = errand.Location,
ASAP = errand.ASAP,
StartDateTime = errand.StartDateTime,
EndDateTime = errand.EndDateTime,
DurationInHours = errand.DurationInHours,
DateTimePosted = errand.DateTimePosted,
PaymentMethod = errand.PaymentMethod,
Currency = errand.Currency,
Offering = errand.Offering,
Price = errand.Price,
FirstName = errand.FirstName,
Rating = errand.Rating
};
var Loaded = new UserActivities
{
ActivityName = "ErrandPreview_Loaded",
ActivityTimeStamp = DateTime.Now.ToUniversalTime(),
UserID = User.Identity.GetUserId()
};
DB.UserActivityList.Add(Loaded);
DB.SaveChanges();
return View(model);
}
public FileContentResult errandPicture()
{
String userID = User.Identity.GetUserId();
var errand = DB.errandList.Where(x => x.UserID == userID)
.OrderByDescending(x => x.DateTimePosted)
.FirstOrDefault();
if (errand.Picture == null)
{
string defaultPicture = Server.MapPath("/Content/errandom_logo_sm.png");
byte[] imageData = null;
FileInfo fileInfo = new FileInfo(defaultPicture);
long imageFileLength = fileInfo.Length;
FileStream fs = new FileStream(defaultPicture, FileMode.Open, FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
imageData = br.ReadBytes((int)imageFileLength);
return File(imageData, "image/png");
}
else
{
return new FileContentResult(errand.Picture, "image/jpeg");
}
}
// GET: EditErrand
public ActionResult EditErrand()
{
var userID = User.Identity.GetUserId();
var errand = DB.errandList.Where(x => x.UserID == userID)
.OrderByDescending(x => x.DateTimePosted)
.FirstOrDefault();
var model = new errands
{
ID = errand.ID,
UserID = User.Identity.GetUserId(),
FirstName = UserManager.FindById(userID).FirstName,
Email = UserManager.FindById(userID).Email,
Phone = UserManager.FindById(userID).PhoneNumber,
//Rating =
Title = errand.Title,
Category = errand.Category,
SubCategory = errand.SubCategory,
Picture = errand.Picture,
Description = errand.Description,
Location = errand.Location,
ASAP = errand.ASAP,
StartDateTime = errand.StartDateTime,
EndDateTime = errand.EndDateTime,
DurationInHours = errand.DurationInHours,
DateTimePosted = errand.DateTimePosted,
PaymentMethod = errand.PaymentMethod,
Currency = errand.Currency,
Offering = errand.Offering,
Price = errand.Price,
Rating = errand.Rating
};
ApplicationDbContext db = new ApplicationDbContext();
ViewBag.categorySelected = db.CategoryList.Select(m => new SelectListItem()
{
Text = m.Category,
Value = m.Category
}).ToList();
ViewBag.subcategorySelected = db.SubCategoryList.Select(m => new SelectListItem()
{
Text = m.SubCategory,
Value = m.SubCategory
}).ToList();
ViewBag.currencySelected = db.CurrencyList.Select(m => new SelectListItem()
{
Text = m.Currency,
Value = m.Currency
}).ToList();
ViewBag.paymentmethodSelected = db.PaymentMethodList.Select(m => new SelectListItem()
{
Text = m.PaymentMethod,
Value = m.PaymentMethod
}).ToList();
var Loaded = new UserActivities
{
ActivityName = "EditErrand_Loaded",
ActivityTimeStamp = DateTime.Now.ToUniversalTime(),
UserID = User.Identity.GetUserId()
};
DB.UserActivityList.Add(Loaded);
DB.SaveChanges();
return View(model);
}
// POST: EditErrand
public ActionResult UpdateErrand([Bind(Exclude = "Picture")]errands model)
{
string newPic = Request.Form["editErrandCroppedPicture"];
byte[] imageBytes = Convert.FromBase64String(newPic);
var userId = User.Identity.GetUserId();
var errand = new errands
{
ID = model.ID,
UserID = User.Identity.GetUserId(),
FirstName = UserManager.FindById(userId).FirstName,
Email = UserManager.FindById(userId).Email,
Phone = UserManager.FindById(userId).PhoneNumber,
Hometown = UserManager.FindById(userId).Hometown,
Rating = model.Rating,
Category = model.Category,
SubCategory = model.SubCategory,
Title = model.Title,
Description = model.Description,
Location = model.Location,
ASAP = model.ASAP,
StartDateTime = model.StartDateTime,
DurationInHours = model.DurationInHours,
EndDateTime = model.EndDateTime,
DateTimePosted = DateTime.UtcNow.ToUniversalTime(),
Currency = model.Currency,
Offering = model.Offering,
Price = model.Price,
errandTax = model.errandTax,
PaymentMethod = model.PaymentMethod,
LatitudePosted = model.LatitudePosted,
LongitudePosted = model.LongitudePosted,
LocationPosted = model.LocationPosted,
UserActivityLatitude = model.LatitudePosted,
UserActivityLongitude = model.LongitudePosted,
UserActivityLocation = model.LocationPosted,
Published = false
};
if (imageBytes.Length > 2)
{
errand.Picture = imageBytes;
}
DB.Entry(errand).State = EntityState.Modified;
DB.SaveChanges();
var Success = new UserActivities
{
ActivityName = "EditErrand_Success",
UserID = User.Identity.GetUserId(),
ActivityTimeStamp = DateTime.Now.ToUniversalTime(),
ActivityLatitude = model.UserActivityLatitude,
ActivityLongitude = model.UserActivityLongitude,
ActivityLocation = model.UserActivityLocation
};
DB.UserActivityList.Add(Success);
DB.SaveChanges();
return RedirectToAction("errandPreview");
}
// POST: errandPreview
public ActionResult ConfirmErrand([Bind(Exclude = "Picture")]errands model)
{
var userId = User.Identity.GetUserId();
var errand = new errands
{
ID = model.ID,
UserID = User.Identity.GetUserId(),
FirstName = UserManager.FindById(userId).FirstName,
Email = UserManager.FindById(userId).Email,
Phone = UserManager.FindById(userId).PhoneNumber,
Hometown = UserManager.FindById(userId).Hometown,
Rating = model.Rating,
Category = model.Category,
SubCategory = model.SubCategory,
Title = model.Title,
Description = model.Description,
Location = model.Location,
ASAP = model.ASAP,
StartDateTime = model.StartDateTime,
DurationInHours = model.DurationInHours,
EndDateTime = model.EndDateTime,
DateTimePosted = DateTime.UtcNow.ToUniversalTime(),
Currency = model.Currency,
Offering = model.Offering,
Price = model.Price,
errandTax = model.errandTax,
PaymentMethod = model.PaymentMethod,
LatitudePosted = model.LatitudePosted,
LongitudePosted = model.LongitudePosted,
LocationPosted = model.LocationPosted,
UserActivityLatitude = model.LatitudePosted,
UserActivityLongitude = model.LongitudePosted,
UserActivityLocation = model.LocationPosted,
Published = true
};
DB.Entry(errand).State = EntityState.Modified;
DB.SaveChanges();
var Success = new UserActivities
{
ActivityName = "errandPreview_Success",
UserID = User.Identity.GetUserId(),
ActivityTimeStamp = DateTime.Now.ToUniversalTime(),
ActivityLatitude = model.UserActivityLatitude,
ActivityLongitude = model.UserActivityLongitude,
ActivityLocation = model.UserActivityLocation
};
DB.UserActivityList.Add(Success);
DB.SaveChanges();
return RedirectToAction("PostErrand");
}
Now I am running into multiple issues and I have been told that I am using the Data Model instead of a View Model, but whenever I try to use a view model (simply replacing errands with a View Model containing the requisite values) it would not work with the code above.
Some of the issues I run into are:
picture is always updated, even when no new value is added, it would override/delete the existing one
instead of changing the record, the code would add a new one (which is not the goal)
change of bool 'Published' does not work at all - it's just being ignored

How to use nested index in RavenDB

I have a chat object with participants as a list. I need to enhance the participants' records with further metadata (eg. fullname, company name, instead of just the user ids).
UPDATE:
ChatLastMessage_Index: is queried only by passing an ExternalChatId or a participantId (hence find a specific chat or all the chats for a specific participant)
Profile_Index: is queried passing a person's name
The goal here would be to decorate the chat's participants with the persons' metadata.
Currently the index used to retrieve a chat and its last message is:
public class ChatLastMessage_Index : AbstractIndexCreationTask<ChatMessage, ChatLastMessage_Index.Result>
{
public class Result
{
public string Id { get; set; }
public string ExternalChatId { get; set; }
public List<Participant> Participants { get; set; }
// last message's information:
public string LastMessageUuid { get; set; }
}
public ChatLastMessage_Index()
{
Map = messages => from message in messages
let chat = LoadDocument<Chat>(message.ChatId)
select new Result
{
Id = chat.Id,
ExternalChatId = chat.ExternalChatId,
Participants = chat.Participants,
LastMessageUuid = message.Id,
LastMessageCreatedBy = message.CreatedBy
};
Reduce = results => from result in results
group result by result.ExternalChatId into g
select new Result
{
Id = g.First().Id,
ExternalChatId = g.First().ExternalChatId,
Participants = g.First().Participants,
LastMessageUuid = g.OrderByDescending(m => m.LastMessageCreatedAt).First().LastMessageUuid
};
}
}
As we have another index (Profile_Index) that retrieves all the participants metadata needed, I was wondering whether would it be possible to re use its results inside the ChatLastMessage_Index in order to avoid having to copy/paste its logic.
Below the second index used to retrieve the participants metadata:
public class Profile_Index : AbstractMultiMapIndexCreationTask<PersonProfile_Index.ReducedResult>
{
public class ReducedResult
{
public string Id { get; set; }
public string Name { get; set; }
public string JobTitle { get; set; }
public string ProfileImageUrl { get; set; }
public string OrganizationId { get; set; }
public string OrganizationName { get; set; }
public string OrgCountryName { get; set; }
public List<LanguageProficiency> Languages { get; set; }
}
public PersonProfile_Index()
{
AddMap<Person>(
persons => from person in persons
select new ReducedResult
{
Id = person.Id,
Name = person.Name,
Languages = person.Languages,
ProfileImageUrl = person.ProfileImage.Url
JobTitle = null,
OrganizationId = null,
OrganizationName = null,
OrgCountryName = null
});
AddMap<Employment>(
employments => from empl in employments
let org = LoadDocument<Organization>(empl.OrganizationId)
let countryName = LoadDocument<Country>(org.CountryOfResidenceId).Name
select new ReducedResult
{
Id = empl.PersonId,
Name = null,
Languages = null,
ProfileImageUrl = null,
JobTitle = empl.JobTitle,
OrganizationId = org.Id,
OrganizationName = org.Name,
OrgCountryName = countryName
});
Reduce = results => from result in results
group result by result.Id into g
select new ReducedResult
{
Id = g.Key,
Name = g.Select(x => x.Name).FirstOrDefault(p => p != null),
JobTitle = g.Select(x => x.JobTitle).FirstOrDefault(p => p != null),
Languages = g.Select(x => x.Languages).FirstOrDefault(p => p != null),
ProfileImageUrl = g.Select(x => x.ProfileImageUrl).FirstOrDefault(p => p != null),
OrganizationId = g.Select(x => x.OrganizationId).FirstOrDefault(p => p != null),
OrganizationName = g.Select(x => x.OrganizationName).FirstOrDefault(p => p != null),
OrgCountryName = g.Select(x => x.OrgCountryName).FirstOrDefault(p => p != null)
};
}
}

How do I retrieve multiple Count fields into a single User Report?

Say I have a User class like this:
public class User
{
public string Id {get; set;}
public string Name {get; set;}
}
Each User can be either a Mentor, a Mentee or both. This is represented by a Relationship class:
public class Relationship
{
public string MentorId {get; set;} // This is a User.Id
public string MenteeId {get; set;} // This is another User.Id
}
Now I would like to generate a report that lists all of my Users and contains a field called Mentor Count and another field called Mentee Count. To achieve this I have created a UserReportDTO class to hold my report data.
public class UserReportDTO
{
public string Name {get; set;}
public string MentorCount {get; set;}
public string MenteeCount {get; set;}
}
I then query my RavenDB to get a list of all the Users and transform this into a list of UserReportDTO instances.
UserService
public List<UserReportDTO> GetReportChunk(
IDocumentSession db,
int skip = 0,
int take = 1024)
{
return db.Query<User>()
.OrderBy(u => u.Id)
.Skip(skip)
.Take(take)
.ToList()
.Select(user =>
new UserReportDTO
{
Name = user.Name,
MentorCount = // What goes here?
MenteeCount = // What goes here?
})
.ToList();
}
As you can see, I am struggling to work out the best way to retrieve the MentorCount and MenteeCount values. I have written some Map/Reduce Indexes that I think should be doing the job but I am unsure how to use them to achieve the result I want.
Question
What is the best way to include multiple aggregate fields into a single query?
EDIT 1
#Matt Johnson: I have implemented your index (see end) and now have a working Report Query which, in case anybody is interested, looks like this:
Working User Report Query
public List<UserDTO> GetReportChunk(IDocumentSession db, Claim claim, int skip = 0, int take = 1024)
{
var results = new List<UserDTO>();
db.Query<RavenIndexes.Users_WithRelationships.Result, RavenIndexes.Users_WithRelationships>()
.Include(o => o.UserId)
.Where(x => x.Claims.Any(c => c == claim.ToString()))
.OrderBy(x => x.UserId)
.Skip(skip)
.Take(take)
.ToList()
.ForEach(p =>
{
var user = db.Load<User>(p.UserId);
results.Add(new UserDTO
{
UserName = user.UserName,
Email = user.Email,
// Lots of other User properties
MentorCount = p.MentorCount.ToString(),
MenteeCount = p.MenteeCount.ToString()
});
});
return results;
}
MultiMap Index
public class Users_WithRelationships :
AbstractMultiMapIndexCreationTask<Users_WithRelationships.Result>
{
public class Result
{
public string UserId { get; set; }
public string[] Claims { get; set; }
public int MentorCount { get; set; }
public int MenteeCount { get; set; }
}
public Users_WithRelationships()
{
AddMap<User>(users => users.Select(user => new
{
UserId = user.Id,
user.Claims,
MentorCount = 0,
MenteeCount = 0
}));
AddMap<Relationship>(relationships => relationships.Select(relationship => new
{
UserId = relationship.MentorId,
Claims = (string[]) null,
MentorCount = 0,
MenteeCount = 1
}));
AddMap<Relationship>(relationships => relationships.Select(relationship => new
{
UserId = relationship.MenteeId,
Claims = (string[]) null,
MentorCount = 1,
MenteeCount = 0
}));
Reduce = results => results.GroupBy(result => result.UserId).Select(g => new
{
UserId = g.Key,
Claims = g.Select(x => x.Claims).FirstOrDefault(x => x != null),
MentorCount = g.Sum(x => x.MentorCount),
MenteeCount = g.Sum(x => x.MenteeCount)
});
}
}
You might be better served with a model that already has your relationship data kept with the user. This might look something like:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string[] MentorUserIds { get; set; }
public string[] MenteeUserIds { get; set; }
}
However, if you want to stick with the model you described, the solution is to get rid of the multiple separate indexes and create a single multi-map index that has the data you need.
public class Users_WithRelationships
: AbstractMultiMapIndexCreationTask<Users_WithRelationships.Result>
{
public class Result
{
public string UserId { get; set; }
public string Name { get; set; }
public int MentorCount { get; set; }
public int MenteeCount { get; set; }
}
public Users_WithRelationships()
{
AddMap<User>(users => from user in users
select new
{
UserId = user.Id,
Name = user.Name,
MentorCount = 0,
MenteeCount = 0
});
AddMap<Relationship>(relationships => from relationship in relationships
select new
{
UserId = relationship.MentorId,
Name = (string)null,
MentorCount = 1,
MenteeCount = 0
});
AddMap<Relationship>(relationships => from relationship in relationships
select new
{
UserId = relationship.MenteeId,
Name = (string)null,
MentorCount = 0,
MenteeCount = 1
});
Reduce = results =>
from result in results
group result by result.UserId
into g
select new
{
UserId = g.Key,
Name = g.Select(x => x.Name).FirstOrDefault(x => x != null),
MentorCount = g.Sum(x => x.MentorCount),
MenteeCount = g.Sum(x => x.MenteeCount)
};
}
}
Then you can update your GetReportChunk method to query against the one index if you still want to project a custom DTO.
return db.Query<Users_WithRelationships.Result, Users_WithRelationships>()
.OrderBy(x => x.UserId)
.Skip(skip)
.Take(take)
.Select(x =>
new UserReportDTO
{
Name = x.Name,
MentorCount = x.MentorCount,
MenteeCount = x.MenteeCount,
})
.ToList();