Entity Framework with Include / Joining Issues - c#

I've implemented an Table Inheritance functionality demonstrated at http://www.sqlteam.com/article/implementing-table-inheritance-in-sql-server.
All the foreign keys and constraints are in place.
Now I am using Entity Framework to pull back my People, Students, Teachers, and Parents where the model looks something like the following (without all the EF specific attributes etc).
public partial class People : EntityObject
{
public guid PeopleID { get; set; }
public int Age { get; set; } /Added for an example query
public PeopleParent Parent { get; set; }
public PeopleStudent Student { get; set; }
public PeopleTeacher Teacher { get; set; }
}
Now I need to get all People regardless of type, who are 25 years old, no more than 100 records, and I want to include all the referenced data. I create my EF Query like:
IQueryable<People> query = Entities.People.Include("PeopleParent")
.Include("PeopleStudent")
.Include("PeopleTeacher");
query.Where(x => x.Age == 25)
.Take(100);
IEnumerable<People> results = query.ToList();
Seems simple enough, but whatever table/entityset I've set to include first creates an INNER JOIN instead of the LEFT OUTER JOIN, which is not producing the right results.
Generated TSQL (incorrect for my needs):
SELECT
[Limit1].[C1] AS [C1],
<A bunch of Limit1 Columns>
FROM (
SELECT TOP (100)
[Extent1].[PeopleID] AS [PeopleID],
<A bunch of Extent1 Columns>
[Extent2].[PeopleID] AS [PeopleID1],
<A bunch of Extent2 Columns>
[Extent3].[PeopleID] AS [PeopleID2],
<A bunch of Extent3 Columns>
[Extent4].[PeopleID] AS [PeopleID3],
<A bunch of Extent4 Columns>
1 AS [C1]
FROM [rets].[People] AS [Extent1]
INNER JOIN [rets].[PeopleParent] AS [Extent2]
ON [Extent1].[PeopleID] = [Extent2].[PeopleID]
LEFT OUTER JOIN [rets].[PeopleStudent] AS [Extent3]
ON [Extent1].[PeopleID] = [Extent3].[PeopleID]
LEFT OUTER JOIN [rets].[PeopleTeacher] AS [Extent4]
ON [Extent1].[PeopleID] = [Extent4].[PeopleID]
) AS [Limit1]
Why is the first Include used as an INNER JOIN, and is there a soluion to my problem?
** UPDATE 1**
Assuming I use Ladislav Mrnka's Answer, there are two additional requirements due to the significant change in Linq and Lambda querying.
Question: How do I search for specific People which have specific properties?
(All Students with a Grade of "A")
Answer:
context.People.OfType<Student>().Where(s => s.Grade == "A");
Question: How do I search for any People that have a specific property?
(All Students or Teachers who's PrimaryFocus = "Math")
Answer:
List<People> result = new List<People>();
result.AddRange(context.People.OfType<Student>()
.Where(x => x.PrimaryFocus == "Math")
.ToList());
result.AddRange(context.People.OfType<Teacher>()
.Where(x => x.PrimaryFocus == "Math")
.ToList());

The obvious solution for you should be using native EF support for inheritance. In your case TPT inheritance. Once you have inheritance you will simply call:
IEnumerable<People> results = Entities.People
.Where(x => x.Age == 25)
.Take(100)
.ToList();
And it will return you instances of Student, Teachers, Parents etc.
In your solution the only advice is check that the relation is optional (1 - 0..1) - . If it is required it will use INNER JOIN. If it is optional and it still uses INNER JOIN it can be some bug or other problem in your model.

Related

Linq GroupBy Clause not including items with zero count

I have a query below which is supposed to group the result by Id, EntityName, DocType, Jurisdiction. For each group the query also returns the ProductList items.
At the moment if the group contains one or more than one product, Then i can see the result giving out a group with a combination of Id,EntityName,DocType,Jurisdiction and ProductList, However if the result doesnt contain products for a particular group i do not see the group at all. What i would like to do is show the groups even if does not have any products in its group. So if the count of ProductList is zero, i would like to set
ProductList= new List NettingAgreementProductDto. Any input would be highly appreciated.
var result = from nae in nettingAgreementEntities.Result
join no in nettingOpinions.Result
on nae.EntityId equals no.EntityId
join np in nettingProducts.Result
on no.ProductId equals np.Id
group np by new
{ nae.EntityId,
nae.EntityName,
nae.DocType,
nae.Jurisdiction
} into g
select new NettingAgreementEntityDto
{
Id = g.Key.EntityId,
EntityName = g.Key.EntityName,
DocType = g.Key.DocType,
Jurisdiction = g.Key.Jurisdiction,
ProductList = g.Select(x => new
NettingAgreementProductDto
{
Id = x.Id,
Name = x.Name
}).ToList()
};
To recap from the comments, currently your query is using Inner Join for associating NettingAgreementEntity with NettingAgreementProducts. This not only multiplies the result set (and thus requires you to use GroupBy after), but also filters out the NettingAgreementEntity without NettingAgreementProducts.
You can achieve the goal by switching to Group Join (or Left Outer Join + GroupBy).
But why entering all these complications. EF navigation properties allow you to almost forget about manual joins, and also allow you to easily see the multiplicity, thus whether you need to group the result or not.
So what I would suggest is to add the currently missing collection navigation property to your NettingAgreementEntity class:
public class NettingAgreementEntity
{
// ...
public virtual ICollection<NettingOpinion> Opinions { get; set; }
}
Optionally do the same for NettingAgreementProduct in case in the future you need something similar for products (it's a many-to-many relationship and should be able to be queried from both sides).
Also I would rename the NettingOpinion class navigation properties NettingAgreementProductNavigation and NettingAgreementEntityNavigation to something shorter, for instance Product and Entity. These names (as well as the names of the collection navigation properties) do not affect the database schema, but IMHO provide better readability.
Once you have that, you'll see that the desired LINQ query is a matter of simple Selects which convert entity class to DTO and let EF query translator produce the necessary joins for you:
var result = db.Set<NettingAgreementEntity>()
.Selec(nae => new NettingAgreementEntityDto
{
Id = nae.EntityId,
EntityName = nae.EntityName,
DocType = nae.DocType,
Jurisdiction = nae.Jurisdiction,
ProductList = nae.Opinions
.Select(no => new NettingAgreementProductDto
{
no.Product.Id,
no.Product.Name,
}).ToList(),
});

Write a LINQ query for 2 tables

I have got 2 tables, Student and CourseTaken. I need to write a LINQ code that displays all CourseTaken, that has Active student status set as true.
I wrote part of the LINQ statement that will display all CourseTaken for a particular Id. How can I further filter it by showing the coursetaken for Active students? (S_ID in CourseTaken contains the student Id.)
List<CourseTaken> courseTakenList =
await dbcont
.CourseTaken
.Where(c => c.CId == courseId)
.ToListAsync();
public class Student
{
public int Id;
public string Name;
public string School;
public bool Active;
}
public class CourseTaken
{
public int CId;
public string CourseName;
public int S_Id;
}
Note: I need to use LINQ and Lambda expressions.
This will give you a list of all courses that has an active student, this assumes you have a navigation property from courses to student called Students
var result = dbcont.CourseTaken.Where(c => c.Students.Any(s => s.Active));
If this is not correct, i think you need to explain your structure better, whether this is Entity framework and you have the appropriate navigation property, and some example data
Update
No, I don't have navigation properties in place. Is there another way
I could get this done ?
Well you probably should, as you are going to have to query the database twice now.
var ids = dbcont.Students.Where(s => s.Active)
.Select(x => x.id)
.ToList();
var result = dbcont.CourseTaken.Where(c => ids.Contains(c.S_Id));
Lastly, take a look at a few entity framework tutorials, your column naming is a little weird, and you really need to hook this up in the spirit of EF. with navigation properties
It sounds to me that you need this query:
from ct in dbcont.CourseTaken
where ct.CId == courseId
join s in dbcont.Student.Where(s => s.Active) on ct.S_Id equals s.Id into gsc
where gsc.Any()
select ct
This is only returning a CourseTaken once, regardless of how many active students are taking the course, as long as their is at least one, of course.
int[] StudentsId =( from s in dbcont.Students
where s.Active ==true
select s.Id).ToArray<int>();
List<CourseTaken> courseTakenList = dbcont.CourseTaken.
Where(c=> StudentsId.Contains(c.S_Id) )
.ToList();
var result =
(from C in db.CourseTakens
join S in db.Students.Where(s => s.Active == true) on C.S_Id equals S.Id
select C
).ToList();
This can get only CourseTaken data. You can add Student data to select clause.

NHibernate 3.3 Right Join QueryOver One-to-One Relationship

So, this is the situation. I have a C# project that uses NHibernate 3.3 (upgrading is not an option), with the following classes:
public class Person{
public List<PersonAddress> PersonAddresses {get;set;}
}
public class NaturalPerson : Person{
public NaturalProperty NaturalProperty {get;set;}
}
public class LegalPerson : Person{
public LegalProperty LegalProperty {get;set;}
}
public class Quarantine{
public Person QuarantinePerson {get;set;}
public QuarantineProperty QuarantineProperty {get;set;}
}
What I need to do is to obtain Persons that may or not "be in quarantine" and that satisfy different conditions that involve Quarantine's properties, and legal or natural properties. Basically what I need is a Left Join from Persons, or a Right Join from Quarantines:
Select *
From Quarantine q
Right Join Persons p on p.ID = q.QuarantinePersonID
Left Join NaturalPersons np on p.ID = np.ID
Left Join LegalPersons lp on p.ID = lp.ID
Where q.Property = 1
and np.Property = 1
This is what I have now. I have managed to do the "from" clause that I need, but I'm having serious problems with the "select" and "where" clauses:
Person p = null;
Quarantine q = null;
var results = this.Session.QueryOver<Quarantine>(() => q)
.JoinQueryOver<Person>(() => q.QuarantinePerson, () => p, JoinType.RightOuterJoin)
.SelectList(list => list
.Select(() => p.Id))
.TransformUsing(Transformers.AliasToBean<Person>())
.List<Person>();
This code generates the following query:
`SELECT p1_.PersonID as y0_
FROM BUP.Quarantines this_
right outer join BUP.Persons p1_
on this_.QuarantinePersonID = p1_.PersonID
left outer join BUP.LegalPersons p1_1_
on p1_.PersonID = p1_1_.PersonID
left outer join BUP.NaturalPersons p1_2_
on p1_.PersonID = p1_2_.PersonID`
As I said, the "from" clause is OK. Now the problem is with the Select and Where clauses.
The main problem with the Select is that I need the whole Person object, wether it's a legal or a natural person. I have tried removing the .SelectList from the query, but it throws a PropertyNotFoundException: "Could not find a setter for property 'p' in class 'Person'".
And the problem with the Where clause is that i don't know how to add conditions based on NaturalPerson and LegalPerson's properties. I have no problem filtering Quarantine and Person properties, but haven't yet succedeed to do the same with the other classes.
Also, this query should be as performant as possible, because timeout is a serious problem. I have managed to do other solutions with subqueries and such, but they took too long.
Any help, with any of the two issues, will be very appreciated!
Thanks!!
You can use subqueries mixing with future to execute both queries in one row trip:
var subquery = QueryOver.Of<Quarantine>()
.Select(x => x.QuarantinePerson.Id);
var naturalPersons = Session.QueryOver<NaturalPerson>()
.WithSubquery.WhereProperty(x => x.Id).NotIn(subquery)
//.Where(x => x.NaturalProperty == somehing)
.Future();
var legalPersons = Session.QueryOver<LegalPerson>()
.WithSubquery.WhereProperty(x => x.Id).NotIn(subquery)
//.Where(x => x.LegalProperty == somehing)
.Future();
var persons = naturalPersons.Cast<Person>().Union(legalPersons);

NHibernate N+1 fetch problem

I have a entity and fluent mapping that look like this.
public class Client : EntityWithTypedId<long>
{
[Length(Max=50)]
public virtual string GivenName { get; set; }
public virtual IList<Address> Addresses { get; set; }
}
public class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Schema("dbo");
Table("Client");
Id(x => x.Id, "ClientId").GeneratedBy.Identity();
Map(x => x.GivenName, "GivenName");
HasManyToMany(x => x.Addresses)
.FetchType.Join()
.Cascade.AllDeleteOrphan()
.Table("ClientAddress")
.ParentKeyColumn("ClientId")
.ChildKeyColumn("AddressId")
.AsBag();
}
}
I then execute an ICriteria query like this
return Session.CreateCriteria<Client>()
.CreateAlias("Organisation", "o").SetFetchMode("o", FetchMode.Join)
.CreateAlias("Addresses", "a").SetFetchMode("a", FetchMode.Join)
.Add(expression)
.AddOrder(Order.Asc("Surname")).AddOrder(Order.Asc("GivenName"))
.SetResultTransformer(new DistinctRootEntityResultTransformer())
.SetMaxResults(pageSize)
.SetFirstResult(Pagination.FirstResult(pageIndex, pageSize))
.Future<Client>();
Using NHProf I can see it executes a query like this which should return all client details and addresses
SELECT top 20 this_.ClientId as ClientId5_2_,
this_.GivenName as GivenName5_2_,
addresses4_.ClientId as ClientId,
a2_.AddressId as AddressId,
a2_.AddressId as AddressId0_0_,
a2_.Street as Street0_0_,
a2_.Suburb as Suburb0_0_,
a2_.State as State0_0_,
a2_.Postcode as Postcode0_0_,
a2_.Country as Country0_0_,
a2_.AddressTypeId as AddressT7_0_0_,
a2_.OrganisationId as Organisa8_0_0_,
o1_.OrganisationId as Organisa1_11_1_,
o1_.Description as Descript2_11_1_,
o1_.Code as Code11_1_,
o1_.TimeZone as TimeZone11_1_
FROM dbo.Client this_
inner join ClientAddress addresses4_
on this_.ClientId = addresses4_.ClientId
inner join dbo.Address a2_
on addresses4_.AddressId = a2_.AddressId
inner join dbo.Organisation o1_
on this_.OrganisationId = o1_.OrganisationId
WHERE (o1_.Code = 'Demo' /* #p4 */
and (this_.Surname like '%' /* #p5 */
or (this_.HomePhone = '%' /* #p6 */
or this_.MobilePhone = '%' /* #p7 */)))
ORDER BY this_.Surname asc,
this_.GivenName asc
Which returns all the records as expected
However if i then write code like
foreach(var client in clients)
{
if (client.Addresses.Any())
{
Console.WriteLn(client.Addresses.First().Street);
}
}
I still get an N+1 issue where it does a select on each address. How can I avoid this?
I think you're misunderstanding what's going on here...it's almost always incorrect to use the distinct result transformer in conjunction with paging. Think about it, you're only getting the first 20 rows of a cross-product given that query above. I'm guessing that several of your clients at the end of the list aren't having their collections populated because of this, leading to your N+1 issue.
If you need to do the paging operation, consider using the batch-size hint on your collection mapping to help minimize the N+1 issue.
Note: if your typical use case is to show pages of 20 at a time, set the batch-size to this value.
When you use CreateAlias(collection), SetFetchMode(collection) has no effect.
For a better approach to eager loading of collections, see http://ayende.com/Blog/archive/2010/01/16/eagerly-loading-entity-associations-efficiently-with-nhibernate.aspx

Counting in a Linq Query

I have a fairly complicated join query that I use with my database. Upon running it I end up with results that contain an baseID and a bunch of other fields. I then want to take this baseID and determine how many times it occurs in a table like this:
TableToBeCounted (One to Many)
{
baseID,
childID
}
How do I perform a linq query that still uses the query I already have and then JOINs the count() with the baseID?
Something like this in untested linq code:
from k in db.Kingdom
join p in db.Phylum on k.KingdomID equals p.KingdomID
where p.PhylumID == "Something"
join c in db.Class on p.PhylumID equals c.PhylumID
select new {c.ClassID, c.Name};
I then want to take that code and count how many orders are nested within each class. I then want to append a column using linq so that my final select looks like this:
select new {c.ClassID, c.Name, o.Count()}//Or something like that.
The entire example is based upon the Biological Classification system.
Assume for the example that I have multiple tables:
Kingdom
|--Phylum
|--Class
|--Order
Each Phylum has a Phylum ID and a Kingdom ID. Meaning that all phylum are a subset of a kingdom. All Orders are subsets of a Class ID. I want to count how many Orders below to each class.
select new {c.ClassID, c.Name, (from o in orders where o.classId == c.ClassId select o).Count()}
Is this possible for you? Best I can do without knowing more of the arch.
If the relationships are as you describe:
var foo = db.Class.Where(c=>c.Phylum.PhylumID == "something")
.Select(x=> new { ClassID = x.ClassID,
ClassName = x.Name,
NumOrders= x.Order.Count})
.ToList();
Side question: why are you joining those entities? Shouldn't they naturally be FK'd, thereby not requiring an explicit join?

Categories