Query common contacts (self many to many relationship) in entity framework - c#

I have this classic scenario where I have a User table and a Contact table containing only UserId and ContactId columns (so it is a self many to many relationshsip). What I would like is a query that gives me a list of userIds with number of common contacts with the specified User. In plain old SQL I have the following query (contacts of user and user itself is filtered out to get facebook like friend suggestions):
SELECT COUNT(c1.ContactId) as CommonContact, c2.UserId
from Contacts as c1
inner join Contacts as c2 on c1.ContactId = c2.ContactId
Where c1.UserId = #Id AND c2.UserId != #Id
AND c2.UserId NOT IN (SELECT ContactId from Contacts Where UserId = #Id)
Group By c2.UserId
ORDER BY CommonContact Desc
This simple query works great but I can not figure out how to write the same query in LINQ to Entity, because in the Entity Framework model I have User entity that entity have Contact navigation property but the connection table is not there directly....
Thanks a lot for any help...

Didn't have time and try to run it but something like this should work.
public class Test
{
//simulate an IQueryable
private readonly IQueryable<Person> _people = new List<Person>().AsQueryable();
public void FindContactMatchCount(Guid personId)
{
//we'll need the list of id's of the users contacts for comparison, we don't need to resolve this yet though so
//we'll leave it as an IQueryable and not turn it into a collection
IQueryable<Guid> idsOfContacts = _people.Where(x => x.Id == personId).SelectMany(x => x.Contacts.Select(v => v.Id));
//find all the people who have a contact id that matches the selected users list of contact id's
//then project the results, this anonymous projection has two properties, the person and the contact count
var usersWithMatches = _people
.Where(x => idsOfContacts.Contains(x.Id))
.Select(z => new
{
Person = z, //this is the person record from the database, we'll need to extract display information
SharedContactCount = z.Contacts.Count(v => idsOfContacts.Contains(v.Id)) //
}).OrderBy(z => z.SharedContactCount)
.ToList();
}
}

Related

The Include path expression must refer to a navigation property defined on the type (select data using LINQ)

I was trying to select data using LINQ
and I have a list called "products" and I want just these items that exist in products list
var Owner = db.Owners
.Where(m => m.ID == id)
.Include(m => m.Products.Where(item1 => products.Any(item2 => item2.ProductID == item1.ProductID)).ToList())
.FirstOrDefault();
but I'm getting this error :
System.ArgumentException: 'The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path'
Include is meant to fetch complete rows of a table, inclusive primary and foreign keys.
Usually it is not efficient to fetch the complete rows of a table. For example, suppose you have a database with Schools and Students. There is a one-to-many relation between Schools and Students: every School has zero or more Students, every Student attends exactly one School, namely the School that the foreign key refers to.
If you fetch School [10] with its 2000 Students, then every Student will have a foreign key SchoolId with a value 10. If you use Include and fetch complete Student rows you will transfer this value 10 over 2000 times. What a waste of processing power!
A DbContext has a ChangeTracker object. Whenever you fetch data without using Select, so if you fetch complete rows, then the fetched rows are stored in the ChangeTracker, together with a Clone of it. You get a reference to the Clone (or the original, doesn't matter). When you change properties of the fetched data, you change the value in the Clone. When you call SaveChanges, the values of all properties of all originals in the ChangeTracker are compared with the values in the Clones. The items that are changed are updated in the database.
So if you fetch School [10] with its 2000 Students, you are not only fetching way more data than you will ever use, but you will also store all these Students in the ChangeTracker together with a Cloned Student. If you call SaveChanges for something completely different (change of the telephone number of the School for instance), then all Students are compared by value property by property with their Clones.
Generic rule:
Whenever you fetch data using Entity Framework, always use Select, and Select only the properties that you actually plan to use. Only fetch complete rows and only use Include if you plan to update the fetched data.
Using Select will also solve your problem:
int ownerId = ...
IEnumerable<Product> products = ...
var Owner = db.Owners.Where(owner => owner.ID == ownerId)
.Select(owner => new
{
// Select only the Owner properties that you actually plan to use
Id = owner.Id,
Name = owner.Name,
// get the Products of this Owner that are in variable products
Products = owner.Products
.Where(product => products.Any(p => p.ProductId == product.ProductId)
.Select(product => new
{
// Select only the Product properties that you plan to use
Id = product.Id,
Price = product.Price,
...
// No need to fetch the foreign key, you already fetched the value
// OwnerId = product.OwnerId,
})
.ToList(),
...
})
.FirstOrDefault();
I used automatic types (new {...}). If you really want to create Owner and Properties, use:
var Owner = db.Owners.Where(...)
.Select(owner => new Owner
{
Id = owner.Id,
...
Products = owner.Products.Where(...).Select(product => new Product
{
Id = product.Id,
...
})
.ToList(),
})
.FirstOrDefault();
Try the following:
var productIds = products.Select(x => x.ProductID);
var Owner = db.Owners
.Where(m => m.ID == id)
.Include(m => m.Products.Where(product => productIds.Contains(product.ProductID))
.FirstOrDefault();

Entity Framework procedure call with navigation properties

I want to call a procedure and access the navigation properties afterwards
using (DbContext c = new DbContext())
{
// System.Data.Objects.ObjectResult<Product>
List<Product> products = c.myProcedure(id).Include("ProductOptions").ToList();
// here the .Include() isn't available
}
my current approach is loading each navigation property seperately
using (DbContext c = new DbContext())
{
List<Product> products = c.myProcedure(id).ToList();
foreach(Product p in products)
{
if(!o.ProductOptions.IsLoaded)
p.ProductOptions.Load();
}
}
is working fine but super slow because of the subselect for each item.
Question: Is there a kind of Include() function or something else to speed up my code?
While using entity framework people tend to use Include instead of Select. The result seems to be the same, however there is a slight difference.
The difference between Include and Select is that Include fetches the complete object, while Select fetches only the selected data. Another difference is that your DbContext object remembers the Included data.
Databases are extremely optimized in fetching data. One of the slower parts of the query is the transfer of the selected data from the database management system to your process. Hence it is wise to limit the amount of fetched data.
Suppose you have database with Schools and their Students: a straightforward one-to-many relations: Every School has zero or more Students, every Student studies at exactly one School, using a foreign key to the School he attends.
Let's query School [10] with its thousand Students:
var result = dbContext.Schools
.Include(school => school.Students)
.Where(school => school.Id == 10)
.FirstOrDefault();
The Complete Student is transferred. Every Student will have the same foreign key SchoolId value 10. So this same number is transferred a thousand times (1001 if you also count School.Id), while you already know the value. what a waste of processing power!
When querying data, always use Select to fetch the data. Only use Include if you plan to update the fetched data
var result = dbContext.Schools
.Where(school => school.Id == 10)
.Select(school => new
{
// Select only the School properties you plan to use
Id = school.Id,
Name = school.Name,
Address = school.Address,
Students = school.Students
.Where(student => ...) // only if you don't want all Students
.Select(student => new
{
// again: select only the properties you plan to use
Id = student.Id,
Name = student.Name,
...
// not needed, you already know the value!
// SchoolId = student.SchoolId,
})
.ToList(),
})
.FirstOrDefault();
I've use new to create an anonymous type. This is the most efficient. If you really need to create Schools and Students, use new School and new Student
.Select(school => new School
{
...
Students = schoolStudents.Select(student => new Student
{
...
})
.ToList(),
})
A last remark. include(school.Students) can be found in using System.Data.Entity

Delete subjects which isn't selected anymore

I'm trying to edit subjects which isn't selected by student anymore. This is how my db looks like:
Using editStudent query I'm selecting items which is edited and using studentSubject I'm retrieving subjects from db.
var editStudent = (from st in _db.Students
where st.Id == student.Id
select new Students
{
Id = student.Id,
Name = student.Name,
Surname = student.Surname,
BirthDate = student.BirthDate,
Gender = student.Gender,
Subject = (from q in student.Subject
select q).ToList()
}).FirstOrDefault();
var studentSubjects = (from q in _db.Subjects
where q.StudentId == student.Id
select q.Name).ToList();
How can I delete subjects which isn't selected anymore? (They will no long be in Subject list from first query)
For example:
in db was 2 subject math and english and user changed these and now he
has only math. first query will return only math in list and second
will return both math and english
You can easily select not used Subjects with
var subjectsNotUsed = _db.Subjects.Where(subject => subject.StudentId == null);
Then (if you are using Entity Framework 6) you can use .RemoveRange() so:
_db.Subjects.RemoveRange(subjectsNotUsed);
_db.SaveChanges();
If you use lower version of EF, you need to delete one by one:
foreach (var subject in notUsedSubjects)
{
_db.Subjects.DeleteObject(subject);
}
_db.SaveChanges();
But I think you should change the database schema. Student shouldnt be related to Subject. You should use table Students just for information about students, and table Subjects just for subjects. Third table (basic M:N relation) you will represent that student has some subject, like that:
DB Diagram
This enables you store each subject and student just once in your database.

EF Linq - how to select children in same query?

I have Oracle db with EF 5 on top of it.
Lets say I have tables Company and Orders. I have EF corresponding entities where Company has field
List<Orders> Orders.
I dont want to create an association.
I have a query which is IQuerable and I need to fill the Orders for each company using
order.CompanyId == company.Id
I cant wrap my head around this atm.. Any help will be appreciated.
EDIT: not every company has orders. The orders list could be empty.
I would consider using the .Join() method to include the Orders table. I write this from memory, so please forgive syntax errors.
//I only use this bc I don't know the full schema.
class DTOCompany {
public int ID {get;set;}
public List<Orders> Orders {get;set;}
}
public List<Companies> GetCompaniesOrders() {
using (var db = new Entities()) {
return db.Companies
.AsEnumerable() //may not be needed.
.Join(db.Orders,
c => CompanyId,
o => o.CompanyId,
(c,o) => new { Company = c, Orders = o})
)
.Select(co => new DTOCompany() {
ID = co.Companies.CompanyId,
Orders = co.Orders.ToList()
})
.ToList();
}
}

Order a list of entities based on a field from a separate entity with a relationship

I'm using Entity Framework for my object-relational mapping, and jqGrid for my grid.
I have an Employee entity, that contains a ContactID field. I have a Contact entity that contains fields FirstName and LastName.
I want to display a list of Employees in a grid, and give the user the ability to sort by FirstName and LastName.
Here's what I have right now:
public JsonResult GridData(string sidx, string sord, int page, int rows)
{
var pageIndex = Convert.ToInt32(page) - 1;
var pageSize = rows;
var totalRecords = GetAllEmployees().Count();
var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize);
IQueryable<Employee> employees = GetAllEmployees().
OrderBy(sidx + " " + sord).
Skip(pageIndex * pageSize).
Take(pageSize).ToArray();
...
}
As you can see, this only gives me the ability to sort by fields in the Employee entity, so I can't sort by FirstName and LastName.
How can I modify this to accomplish that?
If there is no other option you would have to use a projection that includes those fields, it could be something like:
GetAllEmployees()
.Select( e => new {
Person = e,
Contact = Contacts.Where( c=> c.Id == e.ContactID).Single()
})
.OrderBy( x => x.Contact.LastName)
.Select( x => x.Person) //back to Employee
.Skip(...)
.Take(...)
This is not terribly efficient, since you'd have to query the Contacts entity set for each employee (so n queries!) - so I'd suggest modifying the initial Linq to Entities query to return you a list of Employees with their associated contact, which can be done with a join, i.e. GetEmployeesWithContact()
The best Linq to Entities style solution would be to include the Contacts when you load the Employees:
var emps = Employees.Include("Contact");
now each Employee in emps will have a Contact Property that you can use in your projection:
IQueryable<Employee> employees = emps.OrderBy(x=> x.Contact.LastName)
.Skip(pageIndex * pageSize).
.Take(pageSize).ToArray();

Categories

Resources