Sitecore Lucene Search With GlassMapper not working - c#

I have been trying to teach myself Sitecore for the past couple of weeks.
At the moment i am trying to create a list of Recipes for users to search through.
However every Recipe contains Ingredients, Lucene returned these Ingredients as strings containing Item ID's. I wanted to have a List of Ingredients in my code so i gave GlassMapper a shot.
So i excluded the Ingredient list in my code from Lucene by changing the name so Lucene couldn't find the field.
I then set-up GlassMapper to fill the Ingredient list. The list stays null however.
How do i make GlassMapper fill this list for me?
My code:
Recipe class
[SitecoreType(TemplateId= "{1CF86642-6EC5-4B26-B8A7-1B2EC41F7783}")]
public class Recipe : SearchResultItem
{
[SitecoreId]
public Guid Id { get { return base.ItemId.Guid; } }
public virtual string RecipeName { get; set; }
public virtual string BookName { get; set; }
public virtual IEnumerable<Ingredient> _Ingredients { get; set; }
public virtual int AmountOfPeople { get; set; }
}
Ingredient class
[SitecoreType(TemplateId = "{730A0D54-A697-4DAA-908A-279CD24A9F41}")]
public class Ingredient : SearchResultItem
{
[SitecoreId]
Guid Id { get; }
[IndexField("Name")]
public virtual string IngredientName { get; set; }
}
GlassMapperScCustom class (I've only edited this method)
public static IConfigurationLoader[] GlassLoaders()
{
var attributes = new SitecoreAttributeConfigurationLoader("Receptenboek");
var loader = new SitecoreFluentConfigurationLoader();
var config = loader.Add<Recipe>();
config.Id(x => x.ItemId);
config.Info(x => x.Language).InfoType(SitecoreInfoType.Language);
config.Info(x => x.Version).InfoType(SitecoreInfoType.Version);
config.Field(x => x._Ingredients);
config.Info(x => x.Uri).InfoType(SitecoreInfoType.Url);
return new IConfigurationLoader[] {attributes, loader };
}
Recipe Controller
[HttpGet]
public ActionResult Index()
{
List<Recipe> recipes;
IQueryable<Recipe> query;
string index = string.Format("sitecore_{0}_index", Sitecore.Context.Database.Name);
var sitecoreService = new SitecoreService(Sitecore.Context.Database.Name);
string search = WebUtil.GetQueryString("search");
using (var context = ContentSearchManager.GetIndex(index).CreateSearchContext())
{
if (!string.IsNullOrEmpty(search))
{
query = context.GetQueryable<Recipe>().Where(p => p.Path.Contains("/sitecore/Content/Home/Recipes/")).Where(p => p.TemplateName == "Recipe").Where(p => p.RecipeName.Contains(search));
}
else
{
search = "";
query = context.GetQueryable<Recipe>().Where(p => p.Path.Contains("/sitecore/Content/Home/Recipes/")).Where(p => p.TemplateName == "Recipe");
}
recipes = query.ToList();
foreach( var r in recipes)
{
sitecoreService.Map(r);
Sitecore.Diagnostics.Log.Audit("SWELF" + r.RecipeName + "- " + r.BookName + " - " + r.AmountOfPeople + " - " + r.Name + "--" + r._Ingredients.Count(), this);
}
}
RecipesViewModel bvm = new RecipesViewModel() { Recipes = recipes, Search = search };
return View(bvm);
}

After playing around for a while i decided to split my search and mapping a little more. I used my Recipe model with Lucene and created a ViewModel to map the fields to with GlassMapper.
The Recipe class did not change.
The Ingredient class did not change.
The GlassMapperScCustom class was not needed so i restored its default.
RecipeViewModel class
After mapping to this class the Ingredient list had the correct amount of ingredients however all its fields where null.
After looking around on the internet a little more i found this stackoverflow post: Why isn't my Enumerable getting populated by Glass.Mapper?
I decided to give the SitecoreFieldType a go and it did the trick!
[SitecoreType(TemplateId = "{1CF86642-6EC5-4B26-B8A7-1B2EC41F7783}", AutoMap = true)]
public class RecipeViewModel : BaseFields
{
[SitecoreId]
public ID Id { get; set; }
public virtual string RecipeName { get; set; }
public virtual string BookName { get; set; }
[SitecoreField(FieldId = "{D1603482-7CBC-4E55-9CCB-E51DC0FC5A0B}", FieldType = SitecoreFieldType.Multilist)]
public virtual IEnumerable<IngredientViewModel> Ingredients { get; set; }
public virtual int AmountOfPeople { get; set; }
}
Recipe Controller
It turned out to be mapping the wrong way aswel. I found an example of a list being mapped to another list using SitecoreService.GetItem<>()
[HttpGet]
public ActionResult Index()
{
List<RecipeViewModel> recipes;
List<Recipe> query;
string index = string.Format("sitecore_{0}_index", Sitecore.Context.Database.Name);
var sitecoreService = new SitecoreService(Sitecore.Context.Database.Name);
string search = WebUtil.GetQueryString("search");
//Search with Lucene
using (var context = ContentSearchManager.GetIndex(index).CreateSearchContext())
{
if (!string.IsNullOrEmpty(search))
{
query = context.GetQueryable<Recipe>().Where(p => p.Path.Contains("/sitecore/Content/Home/Recipes/")).Where(p => p.TemplateName == "Recipe").Where(p => p.RecipeName.Contains(search)).ToList();
}
else
{
search = "";
query = context.GetQueryable<Recipe>().Where(p => p.Path.Contains("/sitecore/Content/Home/Recipes/")).Where(p => p.TemplateName == "Recipe").ToList();
}
}
//Map to ViewModel
recipes = query.Select(x => sitecoreService.GetItem<RecipeViewModel>(x.ItemId.Guid)).ToList();
RecipesViewModel bvm = new RecipesViewModel() { Recipes = recipes, Search = search };
return View(bvm);
}
One more problem
Because my ViewModel does not inherit from SearchResultItem a lot of useful fields where lost in the mapping. To keep the fields I needed from the SearchResultItem I made a BaseFields class for my ViewModel to inherit. I only needed Url for now but this can be easily expanded with more fields.
public class BaseFields
{
public virtual string Url { get; set; }
}

Related

Querying Mongo when a document have one to many relationship in a single well structed query using mongo C# driver

I'll try to explain it as simple as possible.
We have these 5 very simple classes. Any class that not end with the DTO suffix represent a real document living inside a mongo collection.
public class TruckSingleDriver
{
public string Id { get; set; }
public string DriverId { get; set; }
}
public class TruckSingleDriverDTO
{
public string Id { get; set; }
public Driver Driver { get; set; }
}
public class TruckManyDrivers
{
public string Id { get; set; }
public IEnumerable<string> DriversIds { get; set; }
}
public class TruckManyDriversDTO
{
public string Id { get; set; }
public IEnumerable<Driver> Drivers { get; set; }
}
public class Driver
{
public string Id { get; set; }
}
the simplest way to get TruckSingleDriverDTO with one query will be as follow:
public TruckSingleDriverDTO GetTruckSingleDriverDTO(string truckId)
{
var truckCollection = mongo.GetDatabase("mydb").GetCollection<TruckSingleDriver>("Trucks");
var driverCollection = mongo.GetDatabase("mydb").GetCollection<Driver>("Drivers");
TruckSingleDriverDTO truckDTO = truckCollection.AsQueryable()
.Where(truck => truck.Id == truckId)
.Join(driverCollection, truck => truck.DriverId, driver => driver.Id,
(truck, driver) => new { Id = truck.Id, Driver = driver })
.ToEnumerable() //needed although it seems not
.Select(res => new TruckSingleDriverDTO() { Id = res.Id, Driver = res.Driver })
.Single();
return truckDTO;
}
What i want to achieve is to get TruckManyDriversDTO in a single query, is there away to do it?
public TruckManyDriversDTO GetTruckManyDriversDTO(string truckId)
{
var trucks = mongo.GetDatabase("mydb").GetCollection<TruckManyDrivers>("Trucks");
var drivers = mongo.GetDatabase("mydb").GetCollection<Driver>("Drivers");
/*
* here i need your help
* keep in mind that i want it in a single query
* below this, ill show the simple way to achieve it with 2 queries
*/
TruckManyDrivers truck = trucks.Find(t => t.Id == truckId).Single();
IEnumerable<Driver> driverList = drivers.Find(d => truck.DriversIds.Contains(d.Id)).ToEnumerable();
return new TruckManyDriversDTO() { Id = truck.Id, Drivers = driverList };
}
I got help from this site: https://www.csharpschool.com/blog/linq-join
The best solution I could come up with:
public TruckManyDriversDTO GetTruckManyDriversDTO(string truckId)
{
var Trucks = mongo.GetDatabase("mydb").GetCollection<TruckManyDrivers>("Trucks").AsQueryable();
var Drivers = mongo.GetDatabase("mydb").GetCollection<Driver>("Drivers").AsQueryable();
var query = from truck in Trucks where truck.Id == truckId
let truckDrivers = from driver in Drivers
where truck.DriversIds.Contains(driver.Id) select driver
select new { Truck = truck, Drivers = truckDrivers };
TruckManyDriversDTO dto = query.Select(a => new TruckManyDriversDTO() { Id = a.Truck.Id, Drivers = a.Drivers } ).Single();
return dto;
}

Query separate collection in RavenDB Index (WHERE IN)

Using RavenDB v4.2 or higher, I want to setup an index that queries another collection. Basically, reproduce a WHERE IN clause in the mapping part of the index.
The models below represent two collections. Here each User has a collection of Device ID's:
class Device {
public string Id { get; set; }
public string Name { get; set; }
}
class User {
public string Id { get; set; }
public string BlogPostId { get; set; }
public List<string> DeviceIds { get; set; }
}
Now consider the following index as an example on what I'm trying to achieve:
public class DeviceIndex : AbstractIndexCreationTask<Device, DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
Map = devices => from d in devices
select new Result
{
Id = d.Id,
DeviceName = d.Name,
HasUser = ... ?, // How to get this from Users collection?
UserCount = ... ? // same...
};
}
How do I fill the HasUser true/false and UserCount properties in this index? E.g. how can I query the 'User' collection here?
Please note that this example is seriously simplified for brevity. I'm not so much interested in workarounds, or changing the logic behind it.
As #Danielle mentioned you need to use a mutli-map-index and reduce the result.
Here is a working example
public class DeviceIndex : AbstractMultiMapIndexCreationTask<DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
AddMap<User>(users => from u in users
from deviceId in u.DeviceIds
let d = LoadDocument<Device>(deviceId)
select new Result
{
Id = d.Id,
HasUser = true,
UserCount = 1,
DeviceName = d.Name,
});
AddMap<Device>(devices => from d in devices
select new Result
{
Id = d.Id,
HasUser = false,
UserCount = 0,
DeviceName = d.Name,
});
Reduce = results => from result in results
group result by new { result.Id } into g
select new Result
{
Id = g.First().Id,
DeviceName = g.First().DeviceName,
HasUser = g.Any(e => e.HasUser),
UserCount = g.Sum(e => e.UserCount),
};
}
}
and you can call it like this
var result = await _session.Query<DeviceIndex.Result, DeviceIndex>().ToListAsync();
If you would have a Users List in the Device class List<string> Users
a list that contains the document ids from the Users collection then you could Index these Related documents.
See:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
Or do the opposite,
Create an index on the Users collection, and index the related Device info
Without changing current models,
You can create a Multi-Map Index to index data from different collections.
https://ravendb.net/docs/article-page/4.2/csharp/indexes/multi-map-indexes
https://ravendb.net/docs/article-page/4.2/csharp/studio/database/indexes/create-multi-map-index
https://ravendb.net/learn/inside-ravendb-book/reader/4.0/10-static-indexes-and-other-advanced-options#querying-many-sources-at-once-with-multimap-indexes

Get objects whose property does not exist in enumerable

Multiple answers have led me to the following 2 solutions, but both of them do not seem to be working correctly.
What I have are 2 objects
public class DatabaseAssignment : AuditableEntity
{
public Guid Id { get; set; }
public string User_Id { get; set; }
public Guid Database_Id { get; set; }
}
public class Database : AuditableEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Server { get; set; }
public bool IsActive { get; set; }
public Guid DatabaseClientId { get; set; }
}
Now, the front-end will return all selected Database objects (as IEnumerable) for a given user. I am grabbing all current DatabaseAssignments from the database for the given user and comparing them to the databases by the Database.ID property. My goal is to find the DatabaseAssignments that I can remove from the database. However, my solutions keep returning all DatabaseAssignments to be removed.
if (databases != null)
{
var unitOfWork = new UnitOfWork(_context);
var userDatabaseAssignments = unitOfWork.DatabaseAssignments.GetAll().Where(d => d.User_Id == user.Id);
//var assignmentsToRemove = userDatabaseAssignments.Where(ud => databases.Any(d => d.Id != ud.Database_Id));
var assignmentsToRemove = userDatabaseAssignments.Select(ud => userDatabaseAssignments.FirstOrDefault()).Where(d1 => databases.All(d2 => d2.Id != d1.Database_Id));
var assignmentsToAdd = databases.Select(d => new DatabaseAssignment { User_Id = user.Id, Database_Id = d.Id }).Where(ar => assignmentsToRemove.All(a => a.Database_Id != ar.Database_Id));
if (assignmentsToRemove.Any())
{
unitOfWork.DatabaseAssignments.RemoveRange(assignmentsToRemove);
}
if (assignmentsToAdd.Any())
{
unitOfWork.DatabaseAssignments.AddRange(assignmentsToAdd);
}
unitOfWork.SaveChanges();
}
I think u are looking for an Except extension, have a look at this link
LINQ: Select where object does not contain items from list
Or other way is with contains see below Fiddler link :
https://dotnetfiddle.net/lKyI2F

Lookup data with EF6

Hello i'd like to create relation between EF Poco and DTO.
Here is my situation
I've got these 2 entities in my application
public partial class RFID_TAG
{
public int TAG_ID { get; set; }
public string RFID { get; set; }
public Nullable<int> EMPLOYEE_ID{ get; set; }
public virtual EMPLOYEE EMPLOYEE{ get; set; }
}
public partial class EMPLOYEE
{
public int EMPLOYEE_ID{ get; set; }
public string FIRST_NAME{ get; set; }
public string LAST_NAME{ get; set; }
//ETC...
}
I also have this DTO
public class EMPLOYEELookUpData
{
public int EMPLOYEE_ID{ get; set; }
public string FULL_NAME{ get; set; }
}
I'm using this DTO for specific selects where i only need EMPLOYEE's id and name, I've got CRUD view where user can add new tags it contains datagrid with that contains all tags and textbox thats bound to currently selected tags RFID and combobox which has SelectedItem bound to currently selected tags EMPLOYEE property. This is how i'm selecting data:
private async void GetData()
{
Data = await DbContext.RFID_TAG.Include(x => x.EMPLOYEE).ToListAsync();
EmployeesList = await DbContext.MPLOYEE.Where(x => x.ACTIVE == 1)
.Select(x => new EMPLOYEELookUpData{EMPLOYEE_ID = x.EMPLOYEE_ID, FULL_NAME= x.FIRST_NAME + " " + x.LAST_NAME})
.ToListAsync();
}
But i can't figure how to make relation between EMPLOYEE and EMPLOYEELookUpData so that EF knows how to convert EMPLOYEELookUpData to EMPLOYEE.
I believe you can use AutoMapper for this: https://www.nuget.org/packages/AutoMapper/. It can be installed using Nuget.
The code would look something like this:
using (MyEntities myEntities = new MyEntities())
{
List<EMPLOYEELookUpData> employeeLookupData;
try
{
employeeLookupData = myDB
.Employee
.Select(EMPLOYEELookUpData)
.Where(c => x => x.ACTIVE == 1)
.ToList();
}
catch (InvalidOperationException e)
{
//Write a log entry
}
I have not tested the code. You would have to create the mappings and create special mappings for : EMPLOYEELookUpData .FullName as it equals EMPLOYEE.FirstName + EMPLOYEE.Surname. You can find out how to do this by reading the documentation or posting another question on here.

View complex Many to Many relationship on ADO.net

I'm trying to update related database on many to many relationship using ADO.net
this is my database design:
as you guys notice, entity framework wont mapping the class_student & subject_course, i've been searching the method and found this website: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
the website told me to make a viewModel, and i do so:
namespace Test.Models.ViewModels
{
public class AssignedStudentData
{
public int ID { get; set; }
public string course_code { get; set; }
public bool Assigned { get; set; }
}
}
It's work flawlessly, but my problem is this line of code:
private void PopulateAssignedStudentData(ms_class ms_class)
{
var allStudent = db.ms_student; //this line is the problem
var ClassStudent = new HashSet<int>(ms_class.ms_student.Select(c => c.ID));
var viewModel = new List<AssignedStudentData>();
foreach (var student in allStudent)
{
viewModel.Add(new AssignedStudentData
{
ID = student.ID,
course_code = student.ms_course.course_name,
Assigned = ClassStudent.Contains(student.ID)
});
}
ViewBag.Students = viewModel;
}
in var allStudent, i've tried to make so the system not generate all the student, but instead, student THAT ASSIGNED WITH A SUBJECT so for example:
private void PopulateAssignedStudentDataBySubject(ms_class ms_class, int subject_id)
{
//var allStudent = db.ms_student; //this line is the problem
//My Version:
var allStudentByCourse = db.ms_student.Include(m => m.ms_course).Where(m => m.ms_course.ms_subject.subject_id == subject_id); //this code is not working
var ClassStudent = new HashSet<int>(ms_class.ms_student.Select(c => c.ID));
var viewModel = new List<AssignedStudentData>();
foreach (var student in allStudentByCourse )
{
viewModel.Add(new AssignedStudentData
{
ID = student.ID,
course_code = student.ms_course.course_name,
Assigned = ClassStudent.Contains(student.ID)
});
}
ViewBag.Students = viewModel;
}
i think the code won't work because the ms_course and ms_subject is a many-to-many relationship..
Thank you very much
Class
public partial class ms_course
{
public ms_course()
{
this.ms_student = new HashSet<ms_student>();
this.ms_subject = new HashSet<ms_subject>();
}
public int course_id { get; set; }
public string course_code { get; set; }
public string course_name { get; set; }
public virtual ICollection<ms_student> ms_student { get; set; }
public virtual ICollection<ms_subject> ms_subject { get; set; }
}
I understand that you're looking for students having a course that has at least one specific subject assigned to it. That would be:
db.ms_student
.Where(s => s.ms_course.ms_subject
.Any(sb => sb.subject_id == subject_id)))
It always helps me to articulate the problem clearly in terms of the object model first, as I did in the first sentence. It usually reveals what the query should look like.
What does the error message say?
You can try tis:
var allStudentByCourse = db.ms_student.Include(m => m.ms_course).Include("ms_course.ms_subject").Where(m => m.ms_course.ms_subject.subject_id == subject_id);
alternativ2 (this only works if ms_course has a fk property to ms_subject):
var allStudentByCourse = db.ms_student.Include(m => m.ms_course).Where(m => m.ms_course.subject_id == subject_id);
Update:
var allStudentByCourse = db.ms_student.Include(m => m.ms_course).Include("ms_course.ms_subject").Where(m => m.ms_course.ms_subject.Any(s => s.subject_id == subject_id));

Categories