NHibernate - LINQ Query Many to Many Issue - c#

I'm trying to upgrade an existing application to use NHibernate. My database has the following tables:
Sites:
- Id (PK)
- Name
Categories:
- Id (PK)
- Name
CategoriesSite
- CategoryId (PK)
- SiteId (PK)
- Active
For each category and site a record may or may not exist in the CategoriesSite table. If an item exists in the CategoriesSite table then it can turn the Category off by setting Active to false. If it doesn't then it assumes Active is true.
I'd like to create a LINQ query in NHibernate to filter for categories of a particular site (that are active). For example say I have the following data:
Sites:
Id | Name
1 | Site 1
2 | Site 2
Categories:
Id | Name
1 | Category 1
2 | Category 2
CategoriesSite:
CategoryId | SiteId | Active
1 | 1 | True
1 | 2 | True
2 | 1 | False
I could say:
var categories = session.Query<CategorySite>()
.Where(s => s.Site.Id == 2 && s.Active)
.Select(s => s.Category)
.ToList();
However this will only get Category 1 and not Category 2 which I'd like it to do. I was wondering if anyone has done anything similar and could suggest either a way to query this or offer any recommendations on how I can map this scenario better.

Without seeing the generated query, I can only guess but, try this instead:
var categories = session.Query<CategorySite>()
.Where(s => s.SiteId == 2 && s.Active) // not s.Site.Id
.Select(s => s.Category)
.ToList();

I think i've solved this. I added a one to many collection against the Category for the list of Categorory Sites. This allows me to say:
var categories = session.Query<Category>()
.Where(c => !c.Sites.Any(s => s.Site.Id == 2 && !s.Active))
.ToList();
This means it will only not return the category when it has been set in-active and will still return the Category when no record exists in the CategoriesSite table.

Related

Entity Framework - Query to obtain last 2 unique entries of a column and then their relevant data

I am new to entity framework and I am having difficulty coming up with a query. For the following situation.Say I have the following model
class Student
{
[Key]
public int Id { get; set; }
public string name{get;set;}
public string guid{get;set;}
}
and my table looks like this
id| name | guid
--------------
1 Andrew | C
2 John | D
3 Adam | B
4 Charles| A
5 Jacob | A
Now I would like to get the last two unique GUIDs which will be A and B.
and then I would like to get all row in which these GUIS appeared in the table. so I would like Jacob,Charles and Adam returned. Any suggestions on how I can get started with this ? I know Ill have to sortby then select unique. But I am not sure how Ill do it in Entity.
I think you could do something like this:
var result = dbcontext.Students.OrderByDescending(s => s.Id)
.GroupBy(s => s.Guid)
.Take(2)
.SelectMany(s => s.Take(1));
1- Order by Id descending to get to the last Id's first.
2- Then group by the Guid and take the first 2 groups.
3- Select the top 1 for each group.
You need to only use this if you want output as Jacob,Charles and Adam
Original Credits #Stackberg use suggested query like without Take if the indented result.
//This work against in memory list (TESTED)
var result = dbcontext.Students.OrderByDescending(s => s.Id)
.GroupBy(s => s.Guid)
.Take(2)
.SelectMany(s=>s.ToList());
//IF EF doesn't able to convert the query you can try this.
var inMemoryGrouped = dbcontext.Students.OrderByDescending(s => s.Id)
.GroupBy(s => s.Guid)
.Take(2)
.ToList();
//Flatten the items using select many
var final = inMemoryGrouped.SelectMany(y=>y.ToList());
Only thing it differ from #Stackberg answer is omitted the Take after grouping.

How do I use Group By and MAX in Linq to return multiple rows?

Clarified example
I have a database of users that is created by a script that scans through Active Directory. One of the fields it applies is a "ScanDate" field, which indicates when the scan took place. The script scans through multiple Active Directory domains.
GOAL: Obtain an IList from the database that contains the list of users for ALL domains, but where the ScanDate is the MAX(ScanDate) for each domain.
This ensures I get the freshest data for each domain.
A SQL query that appears to work for me:
SELECT *
FROM ADScans a
WHERE a.ScanDate = (SELECT MAX(b.ScanDate) FROM ADScans b WHERE a.Domain = b.Domain) AND Enabled = 1
However, having trouble getting that expressed in LINQ
e.g.:
Category | Date
Cat1 4/4/16
Cat2 | 4/4/16
Cat3 | 4/4/16
Cat1 | 4/3/16
I would expect a list:
Cat1 | 4/4/16
Cat2 | 4/4/16
Cat3 | 4/4/16
Some clarification
I would expect to have multiple rows returned per category - the MAX(Date) will not just give me one. I am looking to obtain ALL of the rows for the MAX(Date) of each category.
Something like this should work:
var result =
list
//Group by Category
.GroupBy(x => x.Category)
//For each Category, select Category and max Date within the Category
//This would create an anonymous object, you could do a "new Entity" instead if you want
.Select(g => new {Category = g.Key, Date = g.Max(x => x.Date)})
.ToList();
I'm an idiot.
from u in this.db.ADScans
where u.ScanDate ==
(from s in this.db.ADScans where u.Domain == s.Domain select s.ScanDate).Max()
&& u.Enabled
select u;
Rather than using Max(), just order the items in the groups and take the top item: since you ordered the items, it's guaranteed to be the highest one:
var mostRecentScanFromEachDomain =
from u in this.db.ADScans
where u.Enabled
group u by u.Domain into g
select g.OrderByDescending(u => u.ScanDate)
.FirstOrDefault();
You can GroupBy by Domain to get the max ScanDate, then keep only the rows with that Date.
For a model like this:
class ADScan
{
public int Domain { get; set; }
public DateTime ScanDate { get; set; }
}
You can get the scans doing this:
var result = scans.GroupBy(s => s.Domain)
.SelectMany(g => g.Where(s => s.ScanDate == g.Max(d => d.ScanDate)));
This produces a collection containing the scans with the max ScanDate for its Domain.

Linq select one occurrence

how can I use link to fetch one-to-one relation that does not contain duplicates? Example:
ID | STATUS
1 | CHECKIN
2 | CHECKOUT
2 | CHECKOUT
1 | CHECKIN
3 | CHECKOUT <--
I should only retrieve the ID 3 CHECKOUT because he is not duplicated.
Can you help me using linq?
You need to make a Group and ask for only group items that = 1
Dim nonDuplicates = (From x In query Group By x.Id, x.Status Into grp = Group
Where grp.Count = 1)
The other answer will still retrieve all the duplicated items, just removing duplicates of them. If you want to only retrieve non-duplicated items, as you stated in your original question, this will work for you:
Item singles = items.Where(i => !items.Any(j => !i.Equals(j) && i.id == j.id));

NHibernate C# e SQL Server

Good Morning,
I wonder if there's any method within the NHIBERNATE to which I can retrieve the first row of the table?
For example:
Line | ID | Name |Last Name |
1 | 0 | Test | of Information |
2 | 1 | Mauricio | Silva |
If I want the first line or the line 1 of the table
You can use Linq to create queries with nHibernate. There is a method called FirstOrDefault() which takes only the first record. If the query return empty, the FirstOrDefault method will return null, so, remember to check the result before using, for sample:
var firstItem = session.Query<Entity>().FirstOrDefault();
if (firstItem != null)
{
string name = firstItem.Name;
// use object
}
NHibernate does support paging, so we can select "any" record using the .Take() and .Skip(). In our case we can do it like this:
var list = session
.QueryOver<Person>()
.Take(1) // this will take just a first record
//.Skip(0) // we can even skip some, to get next page
;
Then the resulting list will contain 1 or none row...
var person = list.FirstOrDefault();
Also, we can never be sure, what order will be used by DB engine, so we should use explicit ORDER BY:
var list = session
.QueryOver<Contact>()
.OrderBy(p => p.ID)
.Asc
.Take(1)
;
Now we can be sure, that the first result will be with ID == 0

NHibernate Group By parent entity without N+1 query?

I have two tables that have a parent-child relationship. I want to count the records of the child table, grouping them by the parent entity and gather the results. So I want to see how many times each parent entity is referenced in the child table.
So if my parent table is Cats:
| Id | Name |
| 1 | Bob |
| 2 | Garfield |
and the child table is CatSkills:
| Id | Cat_Id | Skill |
| 1 | 1 | Land on feet |
| 2 | 2 | Eat lasagne |
| 3 | 2 | Escape diets |
I want to receive this:
| Id | Name | count of skills |
| 1 | Bob | 1 |
| 2 | Garfield | 2 |
I've tried with NHibernate LINQ, the query seems to be correct, but I get a "feature not supported" exception.
I tried with NHibernate QueryOver, there I get a N+1 problem:
var q = Session.QueryOver<CatSkill>()
.Fetch(s => s.Cat).Eager
.Select(Projections.ProjectionList()
.Add(Projections.Group<CatSkill>(s => s.Cat))
.Add(Projections.RowCount()))
.List<object[]>();
The above query works but will fetch all parent records in separate queries.
In other parts of experimenting I ended up with a SQL exception about how the referenced columns in the SELECT statement are not part of the GROUP BY clause.
Does anyone have an idea on how to implement this query? Thanks!
Update
The updated code, thanks to Radim, looks like this:
// a private class, just to make the query work
class CatDto : Cat
{
public int Count { get; set; }
}
// the actual query code
Cat parent = null;
CatSkill child = null;
CatDto dto = null;
// this is in fact a subselect, which will be injected into parent's SELECT
var subQuery = QueryOver.Of<CatSkill>(() => child)
.Where(() => child.Cat.ID == parent.ID)
.Select(Projections.RowCount());
// this is another subquery to filter out cats without skills
var skillFilterSubQuery = QueryOver.Of<CatSkill>(() => child)
.Where(() => child.Cat.ID == parent.ID /* && more criteria on child table here... */)
.Select(p => p.Cat);
// the alias here is essential, because it is used in the subselect
var query = session.QueryOver<Cat>(() => parent);
// I only want cats with skills
query = query.WithSubquery.WhereExists(skillFilterSubQuery);
query.SelectList(l => l
.Select(p => p.ID).WithAlias(() => dto.ID)
.Select(p => p.Name).WithAlias(() => dto.Name)
// annoying part: I have to repeat the property mapping for all needed properties of parent...
// see the parent.Count property
.Select(Projections.SubQuery(subQuery)).WithAlias(() => dto.Count));
query.TransformUsing(Transformers.AliasToBean<CatDto>());
return query.List<CatDto>();
So this gets rid of the N+1 problem but I have to map every property of the parent class (Cat in the example) manually to the DTO.
It would be nice if I could map it like .Select(s => s) but that throws an Exception saying it can't map the "" property.
An elegant way could be to directly query the parent Cat, and extend it with the required count - as a subselect.
Cat parent = null;
CatSkills child = null;
// this is in fact a subselect, which will be injected into parent's SELECT
var subQuery = QueryOver.Of<CatSkills>(() => child)
.Where(() => child.Cat.ID == parent.ID)
.Select(Projections.RowCount());
// the alias here is essential, because it is used in the subselect
var query = session.QueryOver<Cat>(() => parent);
query.SelectList(l => l
.Select(p => p.ID).WithAlias(() => parent.ID)
.Select(p => p.Name).WithAlias(() => parent.Name)
// see the parent.Count property
.Select(Projections.SubQuery(subQuery)).WithAlias(() => parent.Count)
);
query.TransformUsing(Transformers.AliasToBean<Cat>());
So in this case, we do expect, that Parent does have a property
public virtual int Count { get; set ;}
which is not mapped by NHiberante. If we cannot extend the C# object, we can create some CatDTO (having same properties as Cat entity - plus the Count)

Categories

Resources