I am trying to perform a Join between multiple tables in LINQ. I have the following classes:
Product {Id, ProdName, ProdQty}
Category {Id, CatName}
ProductCategory{ProdId, CatId} //association table
And I use the following code (where product, category and productcategory are instances of the above classes):
var query = product.Join(productcategory, p => p.Id, pc => pc.ProdID, (p, pc) => new {product = p, productcategory = pc})
.Join(category, ppc => ppc.productcategory.CatId, c => c.Id, (ppc, c) => new { productproductcategory = ppc, category = c});
With this code I obtain an object from the following class:
QueryClass { productproductcategory, category}
Where producproductcategory is of type:
ProductProductCategoryClass {product, productcategory}
I do not understand where the joined "table" is, I was expecting a single class that contains all the properties from the involved classes.
My aim is to populate another object with some properties resulting from the query:
CategorizedProducts catProducts = query.Select(m => new { m.ProdId = ???, m.CatId = ???, //other assignments });
how can I achieve this goal?
For joins, I strongly prefer query-syntax for all the details that are happily hidden (not the least of which are the transparent identifiers involved with the intermediate projections along the way that are apparent in the dot-syntax equivalent). However, you asked regarding Lambdas which I think you have everything you need - you just need to put it all together.
var categorizedProducts = product
.Join(productcategory, p => p.Id, pc => pc.ProdId, (p, pc) => new { p, pc })
.Join(category, ppc => ppc.pc.CatId, c => c.Id, (ppc, c) => new { ppc, c })
.Select(m => new {
ProdId = m.ppc.p.Id, // or m.ppc.pc.ProdId
CatId = m.c.CatId
// other assignments
});
If you need to, you can save the join into a local variable and reuse it later, however lacking other details to the contrary, I see no reason to introduce the local variable.
Also, you could throw the Select into the last lambda of the second Join (again, provided there are no other operations that depend on the join results) which would give:
var categorizedProducts = product
.Join(productcategory, p => p.Id, pc => pc.ProdId, (p, pc) => new { p, pc })
.Join(category, ppc => ppc.pc.CatId, c => c.Id, (ppc, c) => new {
ProdId = ppc.p.Id, // or ppc.pc.ProdId
CatId = c.CatId
// other assignments
});
...and making a last attempt to sell you on query syntax, this would look like this:
var categorizedProducts =
from p in product
join pc in productcategory on p.Id equals pc.ProdId
join c in category on pc.CatId equals c.Id
select new {
ProdId = p.Id, // or pc.ProdId
CatId = c.CatId
// other assignments
};
Your hands may be tied on whether query-syntax is available. I know some shops have such mandates - often based on the notion that query-syntax is somewhat more limited than dot-syntax. There are other reasons, like "why should I learn a second syntax if I can do everything and more in dot-syntax?" As this last part shows - there are details that query-syntax hides that can make it well worth embracing with the improvement to readability it brings: all those intermediate projections and identifiers you have to cook-up are happily not front-and-center-stage in the query-syntax version - they are background fluff. Off my soap-box now - anyhow, thanks for the question. :)
What you've seen is what you get - and it's exactly what you asked for, here:
(ppc, c) => new { productproductcategory = ppc, category = c}
That's a lambda expression returning an anonymous type with those two properties.
In your CategorizedProducts, you just need to go via those properties:
CategorizedProducts catProducts = query.Select(
m => new {
ProdId = m.productproductcategory.product.Id,
CatId = m.category.CatId,
// other assignments
});
take look at this sample code from my project
public static IList<Letter> GetDepartmentLettersLinq(int departmentId)
{
IEnumerable<Letter> allDepartmentLetters =
from allLetter in LetterService.GetAllLetters()
join allUser in UserService.GetAllUsers() on allLetter.EmployeeID equals allUser.ID into usersGroup
from user in usersGroup.DefaultIfEmpty()// here is the tricky part
join allDepartment in DepartmentService.GetAllDepartments() on user.DepartmentID equals allDepartment.ID
where allDepartment.ID == departmentId
select allLetter;
return allDepartmentLetters.ToArray();
}
in this code I joined 3 tables and I spited join condition from where clause
note: the Services classes are just warped(encapsulate) the database operations
public ActionResult Index()
{
List<CustomerOrder_Result> obj = new List<CustomerOrder_Result>();
var orderlist = (from a in db.OrderMasters
join b in db.Customers on a.CustomerId equals b.Id
join c in db.CustomerAddresses on b.Id equals c.CustomerId
where a.Status == "Pending"
select new
{
Customername = b.Customername,
Phone = b.Phone,
OrderId = a.OrderId,
OrderDate = a.OrderDate,
NoOfItems = a.NoOfItems,
Order_amt = a.Order_amt,
dis_amt = a.Dis_amt,
net_amt = a.Net_amt,
status=a.Status,
address = c.address,
City = c.City,
State = c.State,
Pin = c.Pin
}) ;
foreach (var item in orderlist)
{
CustomerOrder_Result clr = new CustomerOrder_Result();
clr.Customername=item.Customername;
clr.Phone = item.Phone;
clr.OrderId = item.OrderId;
clr.OrderDate = item.OrderDate;
clr.NoOfItems = item.NoOfItems;
clr.Order_amt = item.Order_amt;
clr.net_amt = item.net_amt;
clr.address = item.address;
clr.City = item.City;
clr.State = item.State;
clr.Pin = item.Pin;
clr.status = item.status;
obj.Add(clr);
}
var query = from a in d.tbl_Usuarios
from b in d.tblComidaPreferidas
from c in d.tblLugarNacimientoes
select new
{
_nombre = a.Nombre,
_comida = b.ComidaPreferida,
_lNacimiento = c.Ciudad
};
foreach (var i in query)
{
Console.WriteLine($"{i._nombre } le gusta {i._comida} y naciĆ³ en {i._lNacimiento}");
}
it has been a while but my answer may help someone:
if you already defined the relation properly you can use this:
var res = query.Products.Select(m => new
{
productID = product.Id,
categoryID = m.ProductCategory.Select(s => s.Category.ID).ToList(),
}).ToList();
Related
I'm trying to join 3 dataset into a List.
PERSONS (physical persons) :
var persons = await _context.Persons.ToListAsync();
USERS (useraccounts) :
var users = await _context.Users.ToListAsync();
WORKCONTRACTS :
I'm using a DTO here to lighten the data.
var workcontractsList = await _context.Workcontracts
.Select(c => new WorkcontractDto()
{
Id = c.Id,
PersonId = c.PersonId,
WorkcontractType = c.WorkcontractType,
StartDate = c.StartDate,
EndDate = c.EndDate
})
.ToListAsync();
Each Person may, or not, have a matching User.
Each Person may, or not, have one or more Workcontracts.
The result should be a list of PersonDto each containing a Person, her Username (coming from User) and a list of Workcontracts.
var query = await from p in persons
join u in users on p.Id equals u.PersonId
join w in workcontractsList on p.Id equals w.PersonId into wlist
select (p => new PersonDto()
{
PersonId = p.Id,
Person = p,
OrigamiUserName = u.UserName,
Workcontracts = wlist.ToList()
});
return query;
I get this error :
CS1941 : C# The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
I tried to remove on of the two joins without success. All the keys used in the joins are of the same type (int) and are enforced at the database level.
Please help.
SOLUTION FOUND :
After reading the comments, I had to admit my approach was wrong. Coming from an Data background, I tend to use "step by step" patterns. I rewrote the query and i works :
var query = await _context.Persons.Select(p => new PersonDto
{
PersonId = p.Id,
Person = p,
OrigamiUserName = p.OrigamiUser.UserName,
Workcontracts = p.Workcontracts.Select(c => new WorkcontractDto
{
Id = c.Id,
PersonId = c.PersonId,
WorkcontractType = c.WorkcontractType,
StartDate = c.StartDate,
EndDate = c.EndDate
}).ToList()
}).ToArrayAsync();
return query;
I guess I could still optimize it more, but, so far, it works as intended.
Thanks for the multiple heads up !
After reading the comments, I had to admit my approach was wrong. Coming from an Data background, I tend to use "step by step" patterns. I rewrote the query and i works :
var query = await _context.Persons.Select(p => new PersonDto
{
PersonId = p.Id,
Person = p,
OrigamiUserName = p.OrigamiUser.UserName,
Workcontracts = p.Workcontracts.Select(c => new WorkcontractDto
{
Id = c.Id,
PersonId = c.PersonId,
WorkcontractType = c.WorkcontractType,
StartDate = c.StartDate,
EndDate = c.EndDate
}).ToList()
}).ToArrayAsync();
return query;
I guess I could still optimize it more, but, so far, it works as intended.
Thanks for the multiple heads up !
I have a webAPI that receives a list of objects. Currently this data is passed to a stored proc as a data table object, but as the dataDump can hold 1000's of ResponseAPI records, this is proving a little slow.
So, I have started to look into the concept of using EF to take the load, pushing all changes in one go using a SaveChanges on the context.
var dataDump = new List<ResponseAPI>
{
new ResponseAPI
{
Id= "1000",
Code = "C15",
value = "1976"
},
new ResponseAPI
{
Id = "999",
Code = "C14",
value = "1976"
}
};
var step2 = from l in dataDump
join p in Plants on new { X1 = l.Code } equals new { X1 = p.Code }
join ps in PlantTypes on new { X1 = l.Code, X2 = p.Id } equals new { X1 = ps.Code, X2= ps.Id}
where ps.TypeName == "Daisy"
select new {
Code = l.Code,
Id = p.Id
};
As far I can tell, this code is working, no errors are produced. What I am trying to obtain is a list of Id's from the dataDump that currently do not exist in the Plants table.
I have had some success using a different technique, but that only had the one table join.
var step1 = dataDump.Where(s =>
s.Code.Any(
a => !context.Plants
.Select(x => x.Code)
.Contains(s.Code)))
.Select(s => s.Code)
.Distinct()
.ToList();
The first snippet "step2" is just doing a basic join between the tables, which works, but I am not sure how to achieve the not! on the joins.
The second snippet "step1" has a not! on the context which does the job of only returning the values from the dataDump what are not in the Plants table.
My perferred method would be step1, but I do not how to add the second join which links on two fields.
var step1 = dataDump.Where(s =>
s.Code.Any(a => !context.Plants.Select(x => x.Code).Contains(s.Code))
&&
s.Code.Any(a => !context.Plants.any(x => x.PlantTypes.Select(t => t.Code).Contains(s.Code)))
).Select(s => s.Code).Distinct().ToList();
This might need some fix but I would like to know if it's really a one to many to many relationship between your entities.
Here is what I am trying to accomplish:
I have a list of companies that offer different services. I am trying to group the services together of a company in a string format so when I can export to excel it shows up in one column.
Right now if a company has 4 services, they will show up 4 different times in query. Which is logical, just want to group them together.
Here is what I have tried and get "A lambda expression with a statement body cannot be converted to an expression tree"
Services = (from cc in CharityCategories join c in Cao_Categories on cc.CategoryID equals c.CategoryID
join chy in CharityYears on cc.CharityYearID equals chy.CharityYearID
where chy.CampYearID == 5 && chy.StatusID == 1
group c by c.Category into cg
select new { Categories = cg.Key.Trim()}).Aggregate(new StringBuilder(), (a, b) =>
{
if (a.Length > 0)
a.Append(",");
a.Append(b.ToString().Split('=')[1].Replace(" }", ""));
return a;
}).ToString() ,
LinqPad shows the error on the line, right after StringBuilder(), "Aggregate(new StringBuilder(), (a, b)"
I can get them to group in a link and when clicked, that link lists them in a format like { myservice = 1} - This is why I am using .Append
use below code then change your logic accordingly, hopefully this will work
Correction in cg.Key.ToString().Trim()
Services = (from cc in CharityCategories
join c in Cao_Categories on cc.CategoryID equals c.CategoryID
join chy in CharityYears on cc.CharityYearID equals chy.CharityYearID
where chy.CampYearID == 5 && chy.StatusID == 1
group c by c.Category into cg
select new { Categories = cg.Key.ToString().Trim() }).Aggregate(new StringBuilder(), (a, b) =>
{
if (a.Length > 0)
a.Append(",");
a.Append(b.ToString().Split('=')[1].Replace(" }", ""));
return a;
}).ToString();
I don't know your requirement, we can correct this if you can provide exact expected result
Well as the error says you cannot convert a lambda expression with a statement body to an expression tree. Entity framework uses expression trees to convert to sql statements. However, you can materialize the results first with (AsEnumerable) and then use your code with linq to objects.
Services = (from cc in CharityCategories
join c in Cao_Categories on cc.CategoryID equals c.CategoryID
join chy in CharityYears on cc.CharityYearID equals chy.CharityYearID
where chy.CampYearID == 5 && chy.StatusID == 1
group c by c.Category into cg
select new { Categories = cg.Key.ToString().Trim() })
.AsEnumerable()
.Aggregate(new StringBuilder(), (a, b) =>
{
if (a.Length > 0)
a.Append(",");
a.Append(b.ToString().Split('=')[1].Replace(" }", ""));
return a;
}).ToString();
Important: The aggregation will take place after all the rows were retrieved from the DB.
Assuming your result set can be represented in the form of the following class
public class Category
{
public string Name{get;set;}
public string Product{get;set;}
}
You could write the following LINQ query to get the result in the way you wished to:
var testList = new List <Category> {
new Category {Name = "A",Product = "decks"},
new Category {Name = "B",Product = "cards"},
new Category {Name = "C",Product = "paper"},
new Category {Name = "A",Product = "scissor"},
new Category {Name = "B",Product = "crates"},
new Category {Name = "C",Product = "rocks"}
};
var finalList = testList
.GroupBy(x => x.Name)
.Select(x => new {
Category =x.Key,
Items = String.Join(",", x.Select(y => y.Product))
});
Thus the result set would be in the form of:
Category A -> decks, scissor
Category B-> cards,crates
Category C-> paper, rocks
Do mark as answer if this helps.
I have this simple code that returns a list of products but now I need to somehow fetch the same lis of products BUT i need to add a new column or value based on a view count.
var products = db.Products.Where(p => p.ProductOwnerUserId == userID).OrderByDescending(p => p.ProductID);
this is what i have so far but i am no expert in LINQ so i was wondering if someone could help me here.
This is a kind of pseudo-code of what i am looking for
var products = from p in db.Products
join pr in db.Reviews on p.ProductID equals pr.ReviewForProductID
into g select new
{
p.*,
ProductView = g.Count(a => a.ReviewForProductID)
};
i have found my OWN answer since nothing came up from you guys... but thanx for the initial tips... im quite new with linq and complexe queries can be hard to understand and fit inside existing code/view
here is my solution:
Thank you for your first answer and well just too bad for the second one that NEVER came... FYI, since my product class is a partial class already a just added another new ProductView.cs partial class containg the new Property and my query (functionnal and tested) looks like this now:
var products = (from p in db.Products
join pr in db.Reviews on p.ProductID equals pr.ReviewForProductID
into g
select new GenericEcomDataAccess.Product
{
ProductID = p.ProductID,
ProductOwnerUserId = p.ProductOwnerUserId,
ProductCurrency = p.ProductCurrency,
ProductDescription = p.ProductDescription,
ProductPrice = p.ProductPrice,
ProductImage = p.ProductImage,
ProductName = p.ProductName,
ProductCount = g.Count()
}).Where(p => p.ProductOwnerUserId == userID)
.OrderByDescending(p => p.ProductID).AsEnumerable();
var products = db.Products.Where(p => p.ProductOwnerUserId == userID)
.OrderByDescending(p => p.ProductID)
.Select(p=> new {Product = p, Count = p.Reviews.Count()});
If you have the foreign keys set up properly
trying to get the concert name, date and venue (which is stored on another table)
but it doesn't seem to be working, but it keeps pulling back null.
var test = from f in db.Concert
where f.Name.StartsWith(concertName)
select new
{
f.Name,
f.StartDateTime,
venues = from v in db.Venues
where v.ID == f.VenueID
select v.Address
}
Edit:
The relationship between Venue and Concert is that Concert has a VenueID that relates to the Venue ID. I need to pass a string back. something like
foreach (var e in test)
{
html += "<div> e.Name +":" + e.Address </div>;
}
You can use group join to get all Venues related to Concert
var test = from f in db.Concert
join v in db.Venues on f.VenueID equals v.ID into g
where f.Name.StartsWith(concertName)
select new
{
f.Name,
f.StartDateTime,
Venues = g.Select(x => x.Address)
};
Usage of results:
foreach (var e in test)
{
// e.Name
// e.StartDateTime
foreach(var address in e.Venues)
// address
}
It looks like it's safe to assume a 1:1 relationship between Concerts and Venues. Given that, you can use join instead.
var test = from f in db.Concert
join v in db.Venues on v.ID equals f.VenueID
where f.Name.StartsWith(concertName)
select new
{
f.Name,
f.StartDateTime,
v.Address
};
If you have a foreign key relationship set up, You don't need to manually query for venues. In that case you get the venues using f.Venue.