I have a problem with a project. I’m trying to get a list of companies that are located in a specific country or city.
Table structure:
Company
CompanyID
CompanyName
etc…
CompanyAddressDetails (relation table)
Company_CompanyID
CorrespondingAddress_AddressID
CorrespondingAddress:
AddressID
StreetName
RegionID
etc…
Region
RegionID
RegionName
RegionRegionTypeID
RegionDetails (relation table)
RegionParent
RegionChild
So to find an address in example Stockholm (which has ID 1198 in the Region table), I would do:
var addresses = from c in db.CorrespondingAddress select c;
addresses = addresses.Where(s => s.RegionID.Equals(1198));
And to find a company in Stockholm I would do:
companyModel = from c in db.Company select c;
companyModel = companyModel.Where(s => s.CorrespondingAddress.Any(x => x.RegionID.Equals(1198)));
But now I want to take into account the RegionDetails table (which has a parent, and child, for example: 1 (Sweden) is parent, and 1198 (Stockholm) is child etc)
How can I do to find a company which is located in Sweden, but has the ID 1198 (Stockholm) in its address row?
In plain SQL I would maybe do something like:
SELECT CompanyName FROM Company
LEFT JOIN CompanyAddressDetails ON (Company.CompanyID = CompanyAddressDetails.Company_CompanyID)
LEFT JOIN CorrespondingAddress ON (CompanyAddressDetails.CorrespondingAddress_AddressID = CorrespondingAddress.AddressID)
LEFT JOIN Region ON (CorrespondingAddress.RegionID = Region.RegionID)
WHERE CorrespondingAddress IN (SELECT RegionChild FROM RegionDetails WHERE RegionParent = 1)
First off, the following code can be refactored into one line:
companyModel = from c in db.Company select c;
companyModel = companyModel.Where(s => s.CorrespondingAddress.Any(x => x.RegionID.Equals(1198)));
Can't you just nest another Any?
companyModel = db.Company.Where(s => s.CorrespondingAddress.Any(x => x.Region.Any(r => r.RegionDetails.Any(rd => rd.Parent == 1 && rd.Child == 1198)));
Edit
Given the following property of CorrespondingAddress:
public virtual Region Region { get; set; }
And assuming Region has a property RegionDetails (making Region -> RegionDetails one-to-one):
public virtual RegionDetails RegionDetails{ get; set; }
The following should work:
companyModel = db.Company.Where(s => s.CorrespondingAddress.Any(x => x.Region.RegionDetails.RegionParent == 1 && x.Region.RegionDetails.RegionChild == 1198)));
since there is no model for the details tables (many-to-many relationship tables), I solved it by using this method:
companyModel = companyModel.Where(s => s.CorrespondingAddress.Any(x => x.Region.RegionParent.Any(d => d.RegionID == region)));
Related
how to get just special 2 attributes from a table to other table( and placement in related textbox) by LINQ??
public string srcht(ACTIVITIES sn)
{
db db = new db();
var q = (from i in db.costumers.Where(i => i.id== sn.id)select i.name).ToList();
return q.Single();
}
public string srcht(ACTIVITIES sn)
{
db db = new db();
var q = (from i in db.costumers.Where(i => i.id== sn.id)select i.family).ToList();
return q.Single();
}
i did linq twice to fill 2 textboxes by name and family in other table. can i do it by one LINQ?
So you have an Activity in sn, and you want the Name and the Family of all Customers that have an Id equal to sn.Id.
var result = db.Customers.Where(customer => customer.Id == sn.Id)
.Select(customer => new
{
Name = customer.Name,
Family = customer.Family,
});
In words: in the database, from the table of Customers, keep only those Customers that have a value for property Id that equals the value of sn.Id. From the remaining Customers select the values of Name and Family.
If you expect that there will only be at utmost one such customer (so if Id is the primary key), just add:
.FirstOrDefault();
If you think there will be several of these customers, add:
.ToList();
The result will be an object, or a list of objects of some anonymous type with two properties: Name and Family.
If you want to mix the Name and the Family in one sequence of strings, which I doubt, consider to put the Name and the Family in an array. Result is sequence of arrays of strings. To make it one array of Strings, use SelectMany:
var result = db.Customers
.Where(customer => customer.Id == sn.Id)
.Select(customer => new string[]
{
customer.Name,
customer.Family,
})
.SelectMany(customer => customer);
Let's say we have these tables:
CAR
ID Name
1 Mustang
2 Taurus
CAR_PART
ID CAR_ID PART_NUMBER
1 1 M772
2 1 A443
3 2 Z889
CAR_COLOR
ID CAR_ID COLOR_NAME
1 1 Red
2 1 Blue
3 2 Yellow
We need to use Linq-to-SQL to get this result:
CAR_ID CAR_NAME CAR_PART_LIST CAR_COLOR_LIST
1 Mustang M772,A443 Red,Blue
How would this be accomplished? I have a new class created with the result column names, and figure a select new MyClass{}; at the end would be good, but am not sure how to handle the multiple groupings for the CAR_PART_LIST and CAR_COLOR_LIST columns.
Any ideas?
edit: here is what I have so far:
from car in db.CAR
join part in db.CAR_PART on car.ID equals part.CAR_ID
join color in db.CAR_COLOR on car.ID equals color.CAR_ID
where car.ID = 1
select new MySearchResult{
CAR_ID = car.ID,
CAR_NAME = car.Name,
CAR_PART_LIST = ?,
CAR_COLOR_LIST = ?
}
public class MySearchResult{
public int CAR_ID { get; set; }
public string CAR_NAME { get; set; }
public string CAR_PART_LIST { get; set; }
public string CAR_COLOR_LIST { get; set; }
public MySearchResult() { }
}
Using the obvious String extension method:
public static string Join(this IEnumerable<string> s, string sep) => String.Join(s, sep);
You can compute the answer by using group join on each related table:
var ans = from car in db.CAR
join part in db.CAR_PART on car.ID equals part.CAR_ID into partj
join color in db.CAR_COLOR on car.ID equals color.CAR_ID into colorj
where car.ID == 1
select new MySearchResult {
CAR_ID = car.ID,
CAR_NAME = car.Name,
CAR_PART_LIST = partj.Select(p => p.PART_NUMBER).Join(","),
CAR_COLOR_LIST = colorj.Select(c => c.COLOR_NAME).Join(",")
};
Do you have foreign keys set up for db.CAR_PART and db.CAR_COLOR? If so, that linq-to-sql will automatically give you properties for the joins. So, it becomes:
var q = from car in db.Car
where car.ID == 1
select new MySearchResult
{
CAR_ID = car.ID,
CAR_NAME = car.Name,
CAR_PART_LIST = String.Join(",", car.CAR_PARTs.Select(cp=>cp.PART_NUMBER))
CAR_PART_LIST = String.Join(",", car.CAR_COLORs.Select(cp=>cp.COLOR_NAME))
};
So you have a table of CarParts, where every CarPart has a CarId and a PartNumber; and you have a table of CarColours, where every CarColour has a Carid and a ColourName.
I assume you do not support invisible cars, so every car has at least one part, and one colour.
You want a sequence of all CarIds, each CarId with the CarName, a list of all CarParts belonging to this CarId(= that have this CarId as foreign key) and a list of all ColourNames belonging to this CarId (again using the foreign key.
To do this, first we get all CarIds with their CarParts and all CarIds with their ColourNames, then we can Join the results on common CarId.
If you think there might be cars without parts or without colours, you need to do a 'Full outer Join' instead of a normal Join. This is not part of standard LINQ, but you can write the extension function yourself. See LINQ Full Outer Join
After the join on common CarId, we Join the result with your Cars on CarId
var partsGroupedByCarId = carParts.GroupBy( // make groups of carParts
carPart => carPart.CarId); // with common CarId as Key
var coloursGroupedByCarId = carColours.GroupBy( // make groups of carColours
carColour => carColour.CarId);, // with common CarId as Key
var joinResult = partsGroupedByCarId.Join( // Join the two groups
coloursGroupedByCarId,
partGroup => partGroup.Key, // from each group of parts take the key (= CarId)
colourGroup => // from each group of colours take the key (= CarId)
(partGroup, colourGroup) => new // when they match make a new object
{
CarId = partGroup.Key, // with the common CarId
CarParts = partGroup // all partNumbers of the car with CarId
.Select(item => item.PartNumber),
CarColours = colourGroup // all colourNames of the car with carId
.Select(item => item.ColourName),
});
Finally a Join of the Cars with all their Colours and Parts:
var result = Cars.Join(joinResult, // Join all cars with the joinResult
car => Id, // from every car take the id
joinedItem => joinedItem.CarId, // from every joinedItem take the CarId
(car, joinedItem) => new // for every car with its matching joinedItem
{ // make a new object
Id = car.Id,
Name = car.Name,
Parts = joinedItem.CarParts.ToList(),
Colours = joinedItem.CarColours.ToList(),
});
TODO: consider creating one big LINQ statements. As all statements use deferred execution I don't think this will improve efficiency. It certainly will decrease readability.
I writed it here dotnetfiddle please check it out:
var q = from car in cars
join part in carparts on car.ID equals part.CAR_ID into parts
join color in carcolors on car.ID equals color.CAR_ID into clrs
where car.ID == 1
select new MySearchResult{
CAR_ID = car.ID,
CAR_NAME = car.Name,
CAR_PART_LIST = String.Join(",",parts.Select(p => p.PART_NUMBER)),
CAR_COLOR_LIST = String.Join(",",clrs.Select(c => c.COLOR_NAME))};
foreach (var item in q)
Console.WriteLine("{0} {1} {2} {3}",
item.CAR_ID,
item.CAR_NAME,
item.CAR_PART_LIST,
item.CAR_COLOR_LIST);
I am trying to query a one to many relationship but cannot figure out how to do this. The problem I have is that the ID of the field I want to filter by lives in the join table (not the main table)...
Its probably easier to illustrate rather than explain!!
The two classes I have are
public class DbUserClient
{
public virtual string UserId { get; set; }
public virtual int ClientId { get; set; }
public virtual DateTime AssignedOn { get; set; }
public virtual DateTime? ClearedOn { get; set; }
// navigation properties
public virtual DbUser User { get; set; }
public virtual DbClient Client { get; set; }
}
and
public class DbClient
{
public virtual int ClientId {get;set;}
public virtual string EntityName { get; set; }
public virtual bool Deleted { get; set; }
// navigation properties
public ICollection<DbUserClient> UserClients { get; set; }
}
In the program I have a repository that exposes the Clients i.e.
public ObservableCollection<DbClient> Clients
{
get { return context.Clients.Local; }
}
I am binding to this which is why I am keen on querying via the Client as this will refresh my "Local" collection. However I can't seem to figure out a way to include the UserClients as well as add the "where" clause.
I have tried something like
context.Clients.Include(c => c.UserClients.Where(uc => uc.UserId == "ME"));
But this results in the following exception
"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"
This works but unfortunately will not update my "Local" collection
from c in context.Clients
from uc in c.UserClients
where uc.ClientId == uc.ClientId && uc.UserId == "ME"
select new { c.ClientId, c.EntityName, uc.AssignedOn };
Any suggestions on where I have gone wrong?
Cheers
Abs
EDIT I : looking at the SQL Profiler the above query generates the following SQL
SELECT
[Extent1].[ClientId] AS [ClientId],
[Extent1].[EntityName] AS [EntityName],
[Extent2].[AssignedOn] AS [AssignedOn]
FROM [dbo].[Client] AS [Extent1]
INNER JOIN [dbo].[UserClient] AS [Extent2] ON [Extent1].[ClientId] = [Extent2]. [ClientId]
WHERE ([Extent2].[ClientId] = [Extent2].[ClientId]) AND (N'ME' = [Extent2].[UserId])
This is pretty simple and more or less along the lines of what I would have written myself if I was handcrafting the SQL
However although the suggested expression below works and as you pointed out populates the Local cache
context.Clients
.Where(c => c.UserClients.Any(uc => uc.UserId == userId))
.Select(c => new { DbClient = c, DbUser = c.UserClients.Where(uc => uc.UserId == userId).FirstOrDefault() }).ToList();
it produces the following SQL. This looks alot more complicated than it needs to be and I am assuming will have performance implications
exec sp_executesql N'SELECT
[Filter2].[ClientId] AS [ClientId],
[Filter2].[EntityName] AS [EntityName],
[Filter2].[Deleted] AS [Deleted],
[Limit1].[UserId] AS [UserId],
[Limit1].[ClientId] AS [ClientId1],
[Limit1].[AssignedOn] AS [AssignedOn],
[Limit1].[ClearedOn] AS [ClearedOn]
FROM (SELECT [Extent1].[ClientId] AS [ClientId], [Extent1].[EntityName] AS [EntityName], [Extent1].[Deleted] AS [Deleted]
FROM [dbo].[Client] AS [Extent1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[UserClient] AS [Extent2]
WHERE ([Extent1].[ClientId] = [Extent2].[ClientId]) AND ([Extent2].[UserId] = #p__linq__0)
) ) AS [Filter2]
OUTER APPLY (SELECT TOP (1)
[Extent3].[UserId] AS [UserId],
[Extent3].[ClientId] AS [ClientId],
[Extent3].[AssignedOn] AS [AssignedOn],
[Extent3].[ClearedOn] AS [ClearedOn]
FROM [dbo].[UserClient] AS [Extent3]
WHERE ([Filter2].[ClientId] = [Extent3].[ClientId]) AND ([Extent3].[UserId] = #p__linq__1) ) AS [Limit1]',N'#p__linq__0 nvarchar(4000),#p__linq__1 nvarchar(4000)',#p__linq__0=N'ME',#p__linq__1=N'ME'
EDIT II : After playing around some more, I have found a solution that seems to fulfill my requirement. Looking at the SQL Profiler, I am happy with the generated SQL. This is similar to that of my orginal query.
exec sp_executesql N'SELECT
[Extent1].[ClientId] AS [ClientId],
[Extent1].[EntityName] AS [EntityName],
[Extent1].[Deleted] AS [Deleted],
[Extent2].[UserId] AS [UserId],
[Extent2].[ClientId] AS [ClientId1],
[Extent2].[AssignedOn] AS [AssignedOn],
[Extent2].[ClearedOn] AS [ClearedOn]
FROM [dbo].[Client] AS [Extent1]
INNER JOIN [dbo].[UserClient] AS [Extent2] ON [Extent1].[ClientId] = [Extent2].[ClientId]
WHERE [Extent2].[UserId] = #p__linq__0',N'#p__linq__0 nvarchar(4000)',#p__linq__0=N'ME'
I am assuming that there is no lazy loading involved here. If someone could confirm I would be grateful
context.Clients.Join
(
context.UserClients,
c => c.ClientId,
uc => uc.ClientId,
(user, usrclient) => new { DbClient = user, DbUserClient = usrclient }
).Where(uc => uc.DbUserClient.UserId == userId).Load();
You can load the clients which have at least one user with UserId = "ME":
var clients = context.Clients
.Where(c => c.UserClients.Any(uc => uc.UserId == "ME"))
.ToList();
This loads the correct clients but no user is included.
If you include the users...
var clients = context.Clients.Include(c => c.UserClients)
.Where(c => c.UserClients.Any(uc => uc.UserId == "ME"))
.ToList();
... you'll get the correctly filtered clients but it will include all users, not only the user "ME".
In order to get the users filtered as well your last approach, the projection, is the best way:
var clientsWithUser = context.Clients
.Where(c => c.UserClients.Any(uc => uc.UserId == "ME"))
.Select(c => new
{
Client = c,
User = c.UserClients.Where(uc => uc.UserId == "ME").FirstOrDefault()
})
.ToList();
This should also update the Local collection because you are loading full entities (Client and User) in the anonymous object list.
Edit
The last query in your question is fine, although it's not really the EF way to write a Join manually when you have navigation properties. The SQL and query result is most likely identical to:
context.UserClients.Include(uc => uc.Client)
.Where(uc => uc.UserId == userId)
.Load();
The Include in this query should translate into the same INNER JOIN that your hand-written LINQ Join produces.
How can I use LINQ/Projection to sort a list of A objects that contain an id field that references table/object B (B contains id and name).
I want to sort list of A objects that contain B by B.name?
Model (pseudo)
public class A
{
public int AId {get; set;}
public Nullable<int> BId {get; set;}
}
public class B
{
public int BId {get;set;}
public string Name {get;set;}
}
Code in some controller passing in a list of A's that contain B's but sort them by B.Name?
var list = db.As.OrderBy(x => x.BId->References.Name); // Way wrong but using something similar
return(list.ToList()
Basically, looking for the equivalent of this (using projection join or OrderBy from above):
var q1 =
from a in db.As
join b in db.Bs on a.BId equals b.BId
orderby b.Name // <- Need this to sort by B's name
select c;
Am I right that you are looking for the equivalent to the LINQ query you have already written above, only that it is based on extension methods instead?
In this case the following should work:
var list = db.As.Where(a => a.BId.HasValue)
.Join(db.Bs, a => a.BId.Value, b => b.BId, (a, b) => new { a, b.Name })
.OrderBy(r => r.Name)
.Select(r => r.a);
I've also added a check to make sure A.BId is not null before getting its value.
Just curious: why can't you use your LINQ query (with the only difference of selecting a instead of c)?
Given the classes A and B where
class A
{
string Name;
Ilist<B> BList;
}
class B
{
string Name;
}
With FluentNH mapping, relationship is many-to-many which is HasManyToMany(x => x.B) for A. B has no reference to A. NH version is 2.1.2.4000.
What should be the linq query to select the collection where each row contains B.Name and count of A's containing that B? Result must be the List of anonymous type who has 2 fields: Name and Count. Result also should include all B's, hence it should be outer join.
My intend is to get the result with minimum round-trips to database, possibly in one go.
If you want to do it in Linq in one hit in code, you could do this...
var result = Session.Linq<A>()
.SelectMany(a => a.BList, (a, b) => new { b.Name, A = a.Id })
.ToList()
.GroupBy(x => x.Name)
.Select(x => new { Name = x.Key, Count = x.Count() })
.ToList();
NHibernate.Linq (2.1.2.4000) can't handle a GroupBy after a SelectMany it seems, so the first ToList pulls all the data into memory. This is inefficient -- a SQL count would be better.
Alternatively, you could add a lazy loaded collection to your B class that goes back to A. If you're using a many-to-many table in the middle, that should be easy.
public class B
{
public virtual string Name { get; set; }
public virtual IList<A> AList { get; private set; }
}
Your query simply becomes...
var result = Session.Linq<B>()
.Where(b => b.AList.Count > 0)
.Select(b => new { b.Name, b.AList.Count }
.ToList();
Which produces very efficient SQL from Linq (using a count) and gives the same result.