I am trying to write a query to find the best match. I have an index with the structure below.
public class UserProfileSearch
{
public int Id { get; set; }
public int Sex { get; set; }
public int Age { get; set; }
public int MaritalStatus { get; set; }
public int CountryLivingIn { get; set; }
public double Height { get; set; }
public double BodyWeight { get; set; }
...
}
When I start my search I use different parameters. I get the search parameters as an object which has the structure below.
public class UserPreference
{
public int Id { get; set; }
public int FromAge { get; set; }
public int ToAge { get; set; }
public int FromHeight { get; set; }
public int ToHeight { get; set; }
public string MartialStatus { get; set; } // This will have id in comma separated form: 11,23,24..
public string CountriesLivingIn { get; set; } // This will also have id in comma separated form: 11,23,24..
public string Sexes { get; set; }
...
}
I am trying to achieve like below.
QueryContainer qCs = null;
userPartnerPreference.CountriesLivingIn.Split(",").ToList().ForEach(id =>
{
qCs |= new TermQuery { Field = "countryLivingIn ", Value = int.Parse(id) };
});
QueryContainer qSs = null;
userPartnerPreference.MartialStatus.Split(",").ToList().ForEach(id =>
{
qSs &= new TermQuery { Field = "maritalStatus", Value = int.Parse(id) };
});
var searchResults = await _elasticClient.SearchAsync<UserProfileSearch>(s => s
.Query(q => q
.Bool(b => b
.Must(qSs)
.Should(
bs => bs.Range(r => r.Field(f => f.Age).GreaterThanOrEquals(userPartnerPreference.FromAge).LessThan(userPartnerPreference.ToAge)),
bs => bs.Range(r => r.Field(f => f.Height).GreaterThanOrEquals(userPartnerPreference.FromHeight).LessThanOrEquals(userPartnerPreference.ToHeight)),
bs => bs.Bool(bsb=>bsb.Should(qCs))
)
)
)
);
I basically want to find the best match result based on the parameters passed ordered by highest number of fields matched. I'm new to elastic search so is this the way to do it?
Note: I have other fields that I need to match. There are around 15 field, which I am planning to have inside should like age and height.
Related
I have a stored proc returning a datatable using a stored procedure. I am able to convert the it to an object using the following code
outgoingPaymentData.AsEnumerable().Select(x => new OutgoingPaymentApprovalDetails() { });
Here is my OutgoingPaymentApprovalDetails class
public class OutgoingPaymentApprovalDetails
{
public int OriginatorId { get; set; }
public string Name { get; set; }
public string DocumentId { get; set; }
public string DebtorName { get; set; }
public string Currency { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public string DebitAccountNo { get; set; }
public string CreditAccountNo { get; set; }
}
Now, instead of a flat list, I need to add heirarchy, to select this one object to 3 objects.
Classes as under:
public class OriginatorDetails
{
public int OriginatorId { get; set; }
public string Name { get; set; }
public List<DocumentDetails> DocumentDetails { get; set; }
}
public class DocumentDetails
{
public string DocumentId { get; set; }
public List<TransactionDetails> TransactionDetails { get; set; }
}
public class TransactionDetails
{
public string Amount { get; set; }
public string DebitAccountNo { get; set; }
public string CreditAccountNo { get; set; }
}
Basically, All Documents of a particular Originator have to be in the list of DocumentDetails and all TransactionDetails of a particular document have to be in that list.
One way is to create a dictionary and add stuff in it and finally create an object. I was wondering if there was a more abbreviated and efficient way to do something like this.
TIA
You can do the grouping of retrieved records of OutgoingPaymentApprovalDetails using Linq to create the nested object of OriginatorDetails collection.
see below code
var originalDetails = inputs.GroupBy(g => g.OriginatorId)
.Select(g => new OriginatorDetails()
{
OriginatorId = g.Key,
Name = g.First().Name,
DocumentDetails = g.GroupBy(d => d.DocumentId)
.Select(d => new DocumentDetails()
{
DocumentId = d.Key,
TransactionDetails = d.Select(t => new TransactionDetails()
{
DebitAccountNo = t.DebitAccountNo,
CreditAccountNo = t.CreditAccountNo,
Amount = t.Amount.ToString()
}).ToList()
})
.ToList()
});
Check the created https://dotnetfiddle.net/FCA7Qc to demostrate your scenario.
Try this code:
Basically you need to group 2 times, first time by OriginatorId and Name and then by DocumentId like this:
var result = list.GroupBy(c => new {c.OriginatorId, c.Name})
.Select(g => new OriginatorDetails()
{
Name = g.Key.Name,
OriginatorId = g.Key.OriginatorId,
DocumentDetails = g
.GroupBy(dd => dd.DocumentId)
.Select(dd => new DocumentDetails()
{
DocumentId = dd.Key,
TransactionDetails = dd.ToList()
.Select(td => new TransactionDetails()
{
Amount = td.Amount.ToString(),
CreditAccountNo = td.CreditAccountNo,
DebitAccountNo = td.DebitAccountNo
}).ToList()
}).ToList()
}).ToList();
Goal: Returning a single object of sum and a list of details.
{ Sum: 1, [ { Amount: 2, Hex: '#123456' }, { Amount: 1, Hex: '#123456' } ] }
Using the below, I cannot achieve the goal:
var data = (await _context.Users
.Where(u => u.Id == userId)
.SelectMany(ue => ue.Expenses)
.Where(ue => ue.CreatedOn.Date <= insightsFilter.To.Value.Date
&& ue.CreatedOn.Date >= insightsFilter.From.Value.Date)
.Include(ue => ue.UserExpenses)
.Include(e => e.Category)
.ToListAsync());
var response = data.Select(e => new GetCategoriesDto {
Sum = e.UserExpenses.Sum(ue => ue.Amount),
Data = data.GroupBy(e => e.Category.Name.ToLower())
.Select(cl => new GetDetailsDto {
Hex = "#123456"
}).ToList()
});
The output is a single array as such:
{ Sum: 3, Data: [ { Sum: 2, Amount: 2, Hex: '#123456' }, { Sum: 1, Amount: 1, Hex: '#123456' } ] }
Where Sum is repeated instead of being on top of the JSON object with a value of 2 + 1 = 3.
Can someone please let me know what I am doing wrong... Thanks!
Data is as follows:
- Users
-- UserExpenses (Junction) - Contains Amount value.
-- Expenses - Contains Category value.
public class Expense
{
[Key]
public int Id { get; set; }
public Category Category { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public List<UserExpenseJunction> UserExpenses { get; set; } = new List<UserExpenseJunction>();
}
public class UserExpenseJunction {
[Key]
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public decimal Amount { get; set; }
public string Currency { get; set; }
}
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public IList<Expense> Expenses { get; set; }
public IList<UserExpenseJunction> UserExpenses { get; set; }
public bool Verified { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
You are doing a Select on your list so of course it will create a GetCategoriesDto for each item the list.
you need to do the sum as a separate item so I think something like this should work (I haven't tried it)
var response = new {
Sum = data.Select(e => e.UserExpenses.Sum(g => g.Amount)).Sum(),
data.Select(e => new GetCategoriesDto {
Data = data.GroupBy(e => e.Category.Name.ToLower())
.Select(cl => new GetDetailsDto {
Amount = cl.Amount.Sum(),
Hex = "#123456"
}).ToArray()
});
It's a little guess by me because you have some missing code in your question.
response is a List, because you created it as a List: var response = data.Select(...).ToList().
Create the reponse as:
var response = new GetCategoriesDto
{
//Sum = sum of amounts, ...
}
I write a MySql join code, and want to retrive same value from the Dotnetcore linq methods.
My Join code is below:
SELECT GL.Id AS GradeLevels,
CRS.Name AS CourseName,
GL.Title AS GradlevelName,
AVG (ASTSTU.ObtainedMarks)
FROM GradeLevels GL
INNER JOIN Courses AS CRS ON CRS.GradeLevelsID = GL.Id
INNER JOIN Units AS UNT ON UNT.CourseID = CRS.ID
INNER JOIN Lessons AS LSN ON LSN.UnitsId = UNT.Id
INNER JOIN Assignments AS AST ON AST.LessonId = LSN.id
INNER JOIN AssignmentStudents AS ASTSTU ON ASTSTU.AssignmentId = AST.id
WHERE CRS.SchoolSystemsID = "08d6a1f2-26df-4ad5-25d3-2a26960aa3fd" -- School System id.
GROUP BY GL.Id;
Now I want to change above MySQL Join into Dotnet core linq method to create an API that will be Showing, I try to write code for this
public async Task<ICollection<GradeLevels>> GetSchoolSystemGradLevelsAverage(Guid schoolSystemId)
{
List<GradeLevels> dashboadOverAllAverage = new List<GradeLevels>();
var dashboadOverAllAverage1 = await _GpsContext.GradeLevels
.Include(d=>d.Departments)
.ThenInclude(c=>c.Courses.Where(s=>s.SchoolSystemsID ==schoolSystemId))
.ThenInclude(u=>u.Units)
.ThenInclude(l=>l.Lessons)
.ThenInclude(a=>a.Assignment)
.ThenInclude(a=>a.assignmentStudents)
.GroupBy(g=>g.ID)
.ToListAsync();
return dashboadOverAllAverage;
}
Now I want to show the data though API and want to call to fields GradeLvels name and Average Marks.
[HttpGet()]
public async Task<IActionResult> GetCEOGradeLevelAverage(string schoolSystemId)
{
var overallgradeAverages = await _ceoDashboadRepository.GetSchoolSystemGradLevelsAverage(Guid.Parse(schoolSystemId));
List<GetGradeLevelAverageVm> getOverallAverageVms = new List<GetGradeLevelAverageVm>();
foreach (GradeLevels overallgradeAverage in overallgradeAverages)
{
getOverallAverageVms.Add(new GetGradeLevelAverageVm
{
Marks = overallgradeAverage.Id.ToString(), //Want to show lable of AvrageMark
Name = overallgradeAverage.Name //Want to show Gradelevel name
});
}
return Ok(getOverallAverageVms);
}
You do select too much from your DB. Here an example, how to select the nessecary values:
using (TestDbContext ctx = new TestDbContext())
{
var tmp = ctx.AssignmentStudents
.Include(s => s.Assignment) // Include all Childs..
.ThenInclude(a => a.Lesson)
.ThenInclude(l => l.Unit)
.ThenInclude(u => u.Course)
.ThenInclude(c => c.GradeLevel)
.Where(a => a.LessonId == 123)
.GroupBy(g => // Group by your Key-Values Grade and Course (You could take names instead of ids. Just for simplification)
new
{
GradeLevel = g.Assignment.Lesson.Unit.Course.GradeLevel.Id,
Course = g.Assignment.Lesson.Unit.Course.Id
})
.Select(s => // Select the result into an anonymous type
new
{
GradeLevels = s.Key.GradeLevel, // with same keys like grouping
Course = s.Key.Course,
AverageObtainedMarks = s.Average(a => a.ObtainedMarks) // and an average ObtainedMarks from objects matching the key
})
.Where(s => s.GradeLevel == 1);
foreach (var t in tmp)
{
Console.WriteLine(t.GradeLevels + " " + t.Course + ": " + t.AverageObtainedMarks);
}
}
Here a the classes and dbcontext I used:
public class GradeLevel
{
public int Id { get; set; }
public List<Course> Courses { get; set; }
}
public class Course
{
public int Id { get; set; }
public int GradeLevelId { get; set; }
public GradeLevel GradeLevel { get; set; }
public List<Unit> Units { get; set; }
}
public class Unit
{
public int Id { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
public List<Lesson> Lessons { get; set; }
}
public class Lesson
{
public int Id { get; set; }
public int UnitId { get; set; }
public Unit Unit { get; set; }
public List<Assignment> Assignments { get; set; }
}
public class Assignment
{
public int Id { get; set; }
public int LessonId { get; set; }
public Lesson Lesson { get; set; }
public List<AssignmentStudent> AssignmentStudents { get; set; }
}
public class AssignmentStudent
{
public int Id { get; set; }
public int AssignmentId { get; set; }
public Assignment Assignment { get; set; }
public decimal ObtainedMarks { get; set; }
}
public class TestDbContext : DbContext
{
public DbSet<AssignmentStudent> AssignmentStudents { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Unit> Units { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<GradeLevel> GradeLevels { get; set; }
}
There are Candidate and Job entities:
public class Candidate
{
public int CandidateId { get; set; }
public string Name { get; set; }
public string SkillTags { get; set; }
public List<string> skillTagsList
{
get
{
return Array.ConvertAll(SkillTags.Split(','), p => p.Trim()).ToList();
}
}
}
public class Job
{
public int JobId { get; set; }
public string Name { get; set; }
public string Company { get; set; }
public string Skills { get; set; }
public List<string> skillsList
{
get
{
return Array.ConvertAll(Skills.Split(','), p => p.Trim()).ToList();
}
}
}
For each job, I want to get the candidates with most matching skills.
This LINQ query returns an error. is there a better LINQ query to get the results?
List<Candidate> candidates = repository.GetCandidates().Result;
List<Job> jobs = repository.GetJobs().Result;
List<Candidate> JobCandidates = null;
jobs.ForEach(j =>
{
JobCandidates = candidates.Where(c => c.skillTagsList.Any(st => j.skillsList.Contains(st.ToLower())));
}
For each job project (.Select) a new object containing the job and the candidate with the top number of matching skills (OrderDescendingBy the number of intersections):
var result = jobs.Select(j => new {
Job = j,
Candidate = candidates.OrderByDescending(c => c.skillTagsList.Intersect(j.skillsList).Count())
.First()
});
I have a EF query:
var result = unitOfWork.deviceInstanceRepository.Get()
.GroupBy(w => new
{
DeviceId = w.DeviceId,
CatalogName = w.Device.CatalogNo,
DeviceName = w.Device.Name,
ManufacturerName = w.Device.Manufacturer1.Name,
})
.Select(s => new InstancesSummary
{
DeviceId = s.Key.DeviceId,
CatalogNo = s.Key.CatalogName,
DeviceName = s.Key.DeviceName,
DeviceManufacturer = s.Key.ManufacturerName,
Quantity = s.Sum(x => x.Quantity)
}).ToList();
At the moment it returns a distinct list of objects with their Count from the database. But I want to modify this query to get the number of devices connected with eg. user 1 and 2.
I know that I can do it by adding this:
List<Expression<Func<DeviceInstance, bool>>> where = new List<Expression<Func<DeviceInstance, bool>>>();
where.Add(w => w.DeviceUsage.UserId == 1);
for user 1, and changing this line:
var result = unitOfWork.deviceInstanceRepository.Get(where)
but in this solution I need to make two queries.
Is there any way to make something like this:
Quantity = s.Sum(x => x.Quantity).Where(w=>w.DeviceUsage.UserId==1)
to get sum of devices connected with user 1 and same to user 2?
#Updated with DevinceInstance class:
public partial class DeviceInstance
{
public int Id { get; set; }
public int DeviceId { get; set; }
public string SerialNo { get; set; }
public System.DateTime CreationDate { get; set; }
public Nullable<int> ProjectId { get; set; }
public bool Issue { get; set; }
public string IssueDetails { get; set; }
public int Quantity { get; set; }
public virtual Device Device { get; set; }
public virtual Project Project { get; set; }
public virtual DeviceUsage DeviceUsage { get; set; }
}
You could try this one:
Quantity = s.Where(w=>w.DeviceUsage.UserId==1 || w.DeviceUsage.UserId==2)
.Sum(x => x.Quantity);