Joining multiple one-to-many tables with Linq to SQL - linq

Well I am trying to join 3 tables here is a brief summary of them
user - id,name,.....
contactdetails - id,detail,....,userId
adress - id,adress,.......contactdetailsId
how do I join these 3 tables with linq to sql?

Write something like (I can't read out the entire structure of the DB from your question):
var q = from a in ctx.address
select new {
a.address,
a.concactdetails.detail,
a.contactdetils.user.name
};
When having one-to-many relationships it's easiest to base the query on the table which "is most many". It is possible to do it the other way around and use LoadWith options. Unfortunately linq-to-sql only supports translating two tables into efficient querys when done that way. If you try it with three tables you will get a load of small fetch-one-line-queries hitting the DB dragging performance down terribly (see http://coding.abel.nu/2011/11/always-check-generated-sql/ for an example).

ContactDetail[] ContactDetails = new ContactDetail[0]; // your contact detail entries
Address[] Addresses = new Address[0]; // your address entries
User[] Users = new User[0]; // your user entries
Users.Join(ContactDetails, user => user.ID, cd => cd.ID, (user, cd) => new { User = user, ContactDetail = cd }).Join(Addresses, UserAndCD => UserAndCD.ContactDetail.ID, address=>address.ContactDetailID, (UserAndCD, address)=> new {User = UserAndCD.User, ContactDetail = UserAndCD.ContactDetail, Address = address});
In this case you will get user-contactdetail-address entries. If you want to get a user with contactdetail enumeration, and an address enumeration for each contactdetail, then you have to use GroupJoin:

Related

Can I join a table to a list using linq? [duplicate]

This question already has answers here:
EntityFramework - contains query of composite key
(12 answers)
Closed 2 years ago.
I have a table as follows:
PersonalDetails
Columns are:
Name
BankName
BranchName
AccountNo
Address
I have another list that contains 'Name' and 'AccountNo'.
I have to find all the records from table that whose respective 'Name' and 'AccountNo' are present in given list.
Any suggestion will be helpful.
I have done following but not of much use:
var duplicationhecklist = dataAccessdup.MST_FarmerProfile
.Join(lstFarmerProfiles,
t => new { t.Name,t.AccountNo},
t1 => new { t1.Name, t1.AccountNo},
(t, t1) => new { t, t1 })
.Select(x => new {
x.t1.Name,
x.t1.BankName,
x.t1.BranchName,
x.t1.AccountNo
}).ToList();
where lstFarmerProfiles is a list.
You probably found out that you can't join an Entity Framework LINQ query with a local list of entity objects, because it can't be translated into SQL. I would preselect the database data on the account numbers only and then join in memory.
var accountNumbers = lstFarmerProfiles.Select(x => x.AccountNo).ToArray();
var duplicationChecklist =
from profile in dataAccessdup.MST_FarmerProfile
.Where(p => accountNumbers
.Contains(p.AccountNo))
.AsEnumerable() // Continue in memory
join param in lstFarmerProfiles on
new { profile.Name, profile.AccountNo} equals
new { param.Name, param.AccountNo}
select profile
So you will never pull the bulk data into memory but the smallest selection you can probably get to proceed with.
If accountNumbers contains thousands of items, you may consider using a better scalable chunky Contains method.
Since you have the lists in .net of values you want to find, try to use the Contains method, for sample:
List<string> names = /* list of names */;
List<string> accounts = /* list of account */;
var result = db.PersonalDetails.Where(x => names.Contains(x.Name) && accounts.Contains(x.AccountNo))
.ToList();
If MST_FarmerProfile is not super large I think you best option is to bring it into memory using AsEnumerable() and do the joining there.
var duplicationhecklist =
(from x in dataAccessdup.MST_FarmerProfile
.Select(z => new {
z.Name,
z.BankName,
z.BranchName,
z.AccountNo
}).AsEnumerable()
join y in lstFarmerProfiles
on new { x.Name, x.AccountNo} equals new { y.Name, y.AccountNo}
select x).ToList();
Since data is usually located on different machines or in separate processes at least: DB - is one and your in-memory list is your app, there is just 2 ways to do it.
Download as small data part from DB to local as possible and join locally (usually using AsEnumerable() or basically ToList()). You got many good thoughts on this in other answers.
Another one is different - upload your local data to server somehow and perform query on DB side. Uploading can be done differently: using some temp tables OR using VALUES. Fortunately there is a small extension for EF now (for both EF6 and EF Core) which you could try. It is EntityFrameworkCore.MemoryJoin (name might be confusing, but it supports both EF6 and EF Core). As stated in author's article it modifies SQL query passed to server and injects VALUES construction with data from your local list. And query is executed on DB server.
If accountNo identifies the record then you could use:
var duplicationCheck = from farmerProfile in dataAccessdup.MST_FarmerProfile
join farmerFromList in lstFarmerProfiles
on farmerProfile.AccountNo equals farmerFromList.AccountNo
select new {
farmerProfile.Name,
farmerProfile.BankName,
farmerProfile.BranchName,
farmerProfile.AccountNo
};
If you need to join on name and account then this should work:
var duplicationCheck = from farmerProfile in dataAccessdup.MST_FarmerProfile
join farmerFromList in lstFarmerProfiles
on new
{
accountNo = farmerProfile.AccountNo,
name = farmerProfile.Name
}
equals new
{
accountNo = farmerFromList.AccountNo,
name = farmerFromList.Name
}
select new
{
farmerProfile.Name,
farmerProfile.BankName,
farmerProfile.BranchName,
farmerProfile.AccountNo
};
If you are only going to go through duplicateChecklist once then leaving .ToList() out will be better for performance.

Linq and Lambda expression for a complex sql query involving joins

Using Linq to Entity (Entity Framework) in MVC 3 project.
My model:
Table - Users
UserID (PK)
...
Table - Clients
ClientID (PK)
Table - PropertyItems
PropertyItemID (PK)
Table - MemberContactPreference (Contains PropertyItems selected by Users- many to many)
UserID(FK)
PropertyItemID(FK)
Table ClientProperties (Contains PropertyItems that belong to Clients - many to many)
ClientID (FK)
PropertyItemID (FK)
I want to list all the distinct users that have selected all the properties selected by clients.
My Approach :
I got a list of all properties for a particular client in
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> UsersMatchingClientProperties = GetAllUsers();
foreach (ClientProperty property in clientproperties)
{
UsersMatchingClientProperties = (from uem in UsersMatchingClientProperties
join ucp in GetAllMemberContactPreferences on
ucp.UserID == uem.UserID
where uem.MemberContactPreferences.SelectMany(
mcp => mcp.PropertyItemID == property.PropertyItemID)
select uem).Distinct;
}
It gives the right result only first time. As it doesn't reduce the number of items in UsersMatchingClientProperties with each iteration. actually it replaces the collection with new resultset. I want to filter out this collection with each iteration.
Also, any suggestions to do this in Lambda expression without using Linq.
Thanks
That generation of an iqueryable in a for loop seems like a dangerous thing, which could end up in a monster sql join being executed at once.
Anyway, I don't think you need that. How about something like this?
// for a given client, find all users
// that selected ALL properties this client also selected
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> allUsers= GetAllUsers();
Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();
Iqueryable<User> UsersMatchingClientProperties = allUsers
.Where(user => allMemberContactProperties
.Where(membP => membP.UserID==user.UserID)
.All(membP => clientProperties
.Select(clientP => clientP.PropertyID)
.Contains(membP.PropertyID)
)
);
Here is an alternative query in case you want the users that selected ANY property for a given client
// for a given client, find all users
// that selected ANY properties this client also selected
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> allUsers= GetAllUsers();
Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();
Iqueryable<User> UsersMatchingClientProperties = clientproperties
.Join(allMembersContactProperties, // join clientproperties with memberproperties
clientP => clientP.PropertyItemID,
membP => membP.PropertyItemID,
(clientP, membP) => membP)) // after the join, ignore the clientproperties, keeping only memberproperties
.Distinct() // distinct is optional here. but perhaps faster with it?
.Join(allUsers, //join memberproperties with users
membP => membP.UserID,
user => user.UserID,
(membP, user) => user)) // after the join, ignore the member properties, keeping only users
.Distinct();
I trust Hugo did a good job suggesting ways to improve your query (+1). But that does not yet explain the cause of your problem, which is the modified closure pitfall.
I think that after your loop there is some code that actually executes the query in UsersMatchingClientProperties. At that moment the query is executed with the last value of the loop variable property! (The loop variable is the closure in each query delegate that is created in an iteration, and it is modified by each iteration).
Change the loop like this:
foreach (ClientProperty property in clientproperties)
{
var property1 = property;
...
and use property1 in the query. That should solve the cause of the problem. But as said, it looks like the whole process can be improved.

Wait for DomainContext.Load<t> from an entityquery with joins to complete (returning new type via 'select new')

My app consolidates data from other DBs for reporting purposes. We can't link the databases, so all the data processing has to be done in code - this is fine as we want to allow manual validation during the imports.
Certain users will be able to start an update through the Silverlight 4 front end.
I have 3 tables in database x that are fed from one EF4 Model (ModelX). I want to join those tables together, select specific columns and return the result as a new entity that exists in a different EF4 Model (ModelY). I'm using this query:
var myQuery = from i in DBx.table1 from it in DBx.table2 from h in DBx.table3 where (i.id==it.id && h.otherid == i.otherid) select new ModelYServer {Name = i.name,Thing = it.thing, Stuff = h.stuff};
The bit i'm stuck on, is how to execute that query, and wait until the Asynchronous call has completed. Normally, i'd use:
DomainContext.Load<T>(myQuery).Completed += (sender,args) =>
{List<T> myList = ((LoadOperation<T>)sender.Entities.ToList();};
but I can't pass myQuery (an IEnumerable) into the DomainContext.Load() as that expects an EntityQuery. The dataset is very large, and is taking up to 30 seconds to return, so I definitely need to wait before continuing.
So can anyone tell me how I can wait for the IEnumerable query to complete, or suggest a better way of doing this (there very likely is one).
Thanks
Mick
One simple way is just to force it to evaluate by calling ToList:
var query = from i in DBx.table1
join it in DBx.table2 on i.id equals it.id
join h in DBx.table3 on i.otherid equals h.otherid
select new ModelYServer {
Name = i.name,
Thing = it.thing,
Stuff = h.stuff
};
// This will block until the results have been fetched
var results = query.ToList();
// Now use results...
(I've changed your where clause into joins on the earlier tables, as that's what you were effectively doing and this is more idiomatic, IMO.)

Can't combine "LINQ Join" with other tables

The main problem is that I recieve the following message:
"base {System.SystemException} = {"Unable to create a constant value of type 'BokButik1.Models.Book-Author'. Only primitive types ('such as Int32, String, and Guid') are supported in this context."}"
based on this LinQ code:
IBookRepository myIBookRepository = new BookRepository();
var allBooks = myIBookRepository.HamtaAllaBocker();
IBok_ForfattareRepository myIBok_ForfattareRepository = new Bok_ForfattareRepository();
var Book-Authors =
myIBok_ForfattareRepository.HamtaAllaBok_ForfattareNummer();
var q =
from booknn in allBooks
join Book-Authornn in Book-Authors on booknn.BookID equals
Book-Authornn.BookID
select new { booknn.title, Book-AuthorID };
How shall I solve this problem to get a class instance that contain with property title and Book-AuthorID?
// Fullmetalboy
I also have tried making some dummy by using "allbooks" relation with Code Samples from the address http://www.hookedonlinq.com/JoinOperator.ashx. Unfortunately, still same problem.
I also have taken account to Int32 due to entity framework http://msdn.microsoft.com/en-us/library/bb896317.aspx. Unfortunatley, still same problem.
Using database with 3 tables and one of them is a many to many relationship. This database is used in relation with entity framework
Book-Author
Book-Author (int)
BookID (int)
Forfattare (int)
Book
BookID (int)
title (string)
etc etc etc
It appears that you are using two separate linq-to-sql repositories to join against. This won't work. Joins can only work between tables defined in a single repository.
However, if you are happy to bring all of the data into memory then it is very easy to make your code work. Try this:
var myIBookRepository = new BookRepository();
var myIBok_ForfattareRepository = new Bok_ForfattareRepository();
var allBooks =
myIBookRepository.HamtaAllaBocker().ToArray();
var Book_Authors =
myIBok_ForfattareRepository.HamtaAllaBok_ForfattareNummer().ToArray();
var q =
from booknn in allBooks
join Book_Authornn in Book_Authors
on booknn.BookID equals Book_Authornn.BookID
select new { booknn.title, Book_AuthorID = Book_Authornn.Book_Author };
Note the inclusion of the two .ToArray() calls.
I had to fix some of you variable names and I made a bit of a guess on getting the Author ID.
Does this work for you?
I would suggest only having a single repository and allowing normal joining to occur - loading all objects into memory can be expensive.
If you are making custom repositories you make also consider making a custom method that returns the title and author IDs as a defined class rather than as anonymous classes. Makes for better testability.

ef and linq extension method

I have this sql that i want to have written in linq extension method returning an entity from my edm:
SELECT p.[Id],p.[Firstname],p.[Lastname],prt.[AddressId],prt.[Street],prt.[City]
FROM [Person] p
CROSS APPLY (
SELECT TOP(1) pa.[AddressId],a.[ValidFrom],a.[Street],a.[City]
FROM [Person_Addresses] pa
LEFT OUTER JOIN [Addresses] AS a
ON a.[Id] = pa.[AddressId]
WHERE p.[Id] = pa.[PersonId]
ORDER BY a.[ValidFrom] DESC ) prt
Also could this be re-written in linq extension method using 3 joins?
Assuming you have set the Person_Addresses table up as a pure relation table (i.e., with no data besides the foreign keys) this should do the trick:
var persons = model.People
.Select(p => new { p = p, a = p.Addresses.OrderByDescending(a=>a.ValidFrom).First() })
.Select(p => new { p.p.Id, p.p.Firstname, p.p.LastName, AddressId = p.a.Id, p.a.Street, p.a.City });
The first Select() orders the addresses and picks the latest one, and the second one returns an anonymous type with the properties specified in your query.
If you have more data in your relation table you're gonna have to use joins but this way you're free from them. In my opinion, this is more easy to read.
NOTE: You might get an exception if any entry in Persons have no addresses connected to them, although I haven't tried it out.

Resources