How to assign to a List<string> in LinqToEntities C# - c#

Hoping that I'm just missing something obvious but here's my query
var data = (from project in _db.Projects
where project.Id == id
let primaryCategory = (from c in _db.Categories
where c.CategoryID == project.PrimaryCategory
select c.Name)
let categories = (from c in _db.ProjectCategories
join pc in _db.Projects_ProjectCategories on c.ProjectCategoryID equals pc.ProjectCategoryID
where pc.ProjectID == project.ProjectID
select c.Name)
let owner = (from o in _db.Owners
join po in _db.App_Projects_Owners on o.OwnerID equals po.OwnerID
where po.ProjectID == project.ProjectID
select new OwnerModel
{
Owner = o,
Project = project,
PrimaryCategory = primaryCategory.FirstOrDefault(),
Categories = categories.ToList()
})
select new
{
owner,
project
}).FirstOrDefault();
In there OwnerModel.Categories is a List of strings. I can't use ToList() in the query because it gives a materialization error. I've added a custom setter that takes the IQueryable, but that still makes another round trip to the database for every owner that the query returns.
So how are you supposed to assign basic lists in a subquery?
EDIT AND ANSWER (since Robert McKee lead me to the answer in his comment).
The answer is to use the group by clause like so
var data = (from project in _db.Projects
where project.Id == id
let primaryCategory = (from c in _db.Categories
where c.CategoryID == project.PrimaryCategory
select c.Name)
let categories = (from c in _db.ProjectCategories
join pc in _db.Projects_ProjectCategories on c.ProjectCategoryID equals pc.ProjectCategoryID
where pc.ProjectID == project.ProjectID
group c.Name by pc.ProjectCategoryID into x
select x.ToList())
let owner = (from o in _db.Owners
join po in _db.App_Projects_Owners on o.OwnerID equals po.OwnerID
where po.ProjectID == project.ProjectID
select new OwnerModel
{
Owner = o,
Project = project,
PrimaryCategory = primaryCategory.FirstOrDefault(),
Categories = categories
})
select new
{
owner,
project
}).FirstOrDefault();
Specifically note the bits involving
group c.Name by pc.ProjectCategoryID into x
select x.ToList()
It may seem counter-intuitive that it works like this until you dig into exactly what's going on. The method that I was calling above with categories.ToList() was trying to use the System.Collection.Generics ToList function, and that list didn't have any way to convert the expression to sql. However by using the group by clause I was creating a specialized Enumerable IGrouping and calling the ToList function on that. This function is able to be translated into a sql statement and thus doesn't throw the exception.
Learn something new every day.

Set up your navigation properties, and then your query becomes something like:
var data=db.Projects
.Include(p=>p.PrimaryCategory)
.Include(p=>p.Categories)
.Include(p=>p.Owner) // or .Include(p=>p.Owners) if projects can have multiple owners
.First(p=>p.Id == id);
As for child objects, you are looking for the group by clause or .GroupBy method.

Related

How do I group one to many in a nested fashion with linq to entities?

I have three tables, Chart, ChartQuestion, ChartAnswer
A Chart has many ChartQuestions, a ChartQuestion has many ChartAnswers.
I'd like to do a linq query that gives me an object containing all of this data, so it'd be a ChartObject containing List<ChartQuestion> and each ChartQuestion contains a List<ChartAnswer>
I started with this:
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId into chartQuestions)
This seems to be the first step. However I want to include the ChartAnswers now, so I have to do another join to pull back the ChartAnswers but I don't know how to do this.
I can't do a join, and while I can do a from, I am not sure of the exact syntax.
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId into chartQuestions
from chartQuestionsSelection in chartQuestions
join chartAnswer in context.ChartAnswers on chartQuestions.ChartAnswerId equals chartAnswer.ChartAnswerId into chartAnswers // This is wrong
)
With that code above you end up as chartAnswers being separate to the chartQuestions rather than belonging to them, so I don't think it is correct.
Any idea?
joining a table destructors it i.e. you now have the row as a variable if you need it in a nested fashion then select in like this
(from chart in db.Chart
select new { //can also be your own class/record, perhaps a DTO
chart.Id,
chart.Name,
questions = (from chartQuestion in db.chartQuestion
where chart.ChartId == chartQuestion.ChartId
select new { //perhaps a dto
chartQuestion.Id,
chartQuestion.Question,
Answers =
(from chartAnswer in context.ChartAnswers
where chartAnswer.ChartAnswerId == chartQuestion.ChartAnswerId
select chartAnswer).ToList() //this is translated and not evaluated
}).ToList() //this is translated not evaluated
}).ToListAsync(cancellationToken) //this will evaluate the expression
If you need it by joins then you can group join it like this:
from m in _context.Chart
join d in _context.ChartQuestions
on m.ID equals d.ID into mdJoin
select new
{
chartId = m.ID,
chartName = "m.name",
quess = from d in mdJoin
join dd in _context.ChartAnswer
on d.Iddomain equals dd.DomainId into anJoin
select new
{
quesId = d.ID,
quesName = d.Question,
anss = anJoin
}
}
A better way: If you edit your DbConfigurations to include navigation properties of each then the code will become way simpler.
example:
db.Chart
.Include(x => x.chartQuestions)
.ThenInclude(x => x.chartAnswers)
You can search more about how to do navigation properties in EFCore, but ofcourse if the code base is large then this might not be feasible for you.
Try following :
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId
join chartAnswer in context.ChartAnswers on chartAnswer.ChartAnswerId equals chartQuestion.ChartAnswerId
)

Linq - how to filter dataset based on data in another table?

I'm looking to filter the data in a dataset much like you'd do a where <value> in (select <value> from other_table where year=2016)
So I have a list of the "values":
var BUs = (from b in dc.BusinessUnits
where b.Year == int.Parse(ddlYears.SelectedValue)
orderby b.BuName
select new { b.BUID }).ToList();
So what I need to do is filter this dataset based on the BUID list returned in the BUs var.
IQueryable<Market> markets = (from p in dc.Markets
orderby p.MarketName
select p);
Help? I'm 100% new to linq so I need a concise solution.
Well if you Market entity has a BUID property and this property it's a primitive type (int, string..) or an enum, you can use Contains method:
var BUs = (from b in dc.BusinessUnits
where b.Year == int.Parse(ddlYears.SelectedValue)
orderby b.BuName
select b.BUID );
IQueryable<Market> markets = (from p in dc.Markets
where BUs.Contains(p.BUID)
orderby p.MarketName
select p);
The standard way of filtering by in memory id list is to use Enumerable.Contains method. But you need first to make sure your list contains ids - they way you wrote it it will contain anonymous type with a property called BUID, by changing the first query like this
int year = int.Parse(ddlYears.SelectedValue);
var BUIDs = (from b in dc.BusinessUnits
where b.Year == year
orderby b.BuName
select b.BUID).ToList();
and then use
var markets = (from p in dc.Markets
where BUIDs.Contains(p.BUID)
orderby p.MarketName
select p);
But note that this will be inefficient. Much better option would be to not use the list of BUIDs for filtering, but combining the 2 queries so the whole thing becomes a single query executed in the database, like this
var markets = (from p in dc.Markets
where dc.BusinessUnits.Any(bu => b.Year == year && b.BUID == p.BUID)
orderby p.MarketName
select p);
This is the exact equivalent of, if using your words, much like you'd do a "where in (select from other_table where year=2016)".

Linq to entities search multiple tables with one query

I've been scratching my head for some hours trying to figure this one out. I need to search in several tables, using Entity Framework 6 and I've tried to make it work using joins with no luck at all. After a while, I decided to simply make it into several queries, which I'm not sure if ideal at all.
The following code works, but it's not pretty, I think (searchTerm is my search query which is a string):
// Search shareholders
var shareholders =
(from sh in ctx.FirmTypeShareholders.Where(x =>
x.CustomerID.ToString().Equals(searchTerm) ||
x.Firm_FirmType.Firm.ID.ToString().Equals(searchTerm))
join firm in ctx.Firms on sh.Firm_FirmType.FirmID equals firm.ID
select sh).ToList();
// Power plants by shareholder id
var powerPlantsByShareholder = (from sh in ctx.FirmTypeShareholders
join firm in ctx.Firms on sh.Firm_FirmType.FirmID equals firm.ID
from o in firm.Ownerships
join ppu in ctx.PowerPlantUnits on o.PowerPlantUnitID equals ppu.ID
join powerPlant in ctx.PowerPlants on ppu.PowerPlantID equals powerPlant.ID
where sh.CustomerID == 34
select new
{
Shareholder = sh,
PowerPlant = powerPlant
});
// Search persons
var persons = (from p in ctx.People.Where(x =>
x.ID.ToString().Equals(searchTerm) || x.Mobile.ToString().Equals(searchTerm) ||
x.Email.Equals(searchTerm) || x.Name.Contains(searchTerm))
select p).ToList();
// Search power plants
var powerPlants = (from pp in ctx.PowerPlants.Where(x =>
x.ID.ToString().Equals(searchTerm) || x.GSRN.ToString().Equals(searchTerm) ||
x.EDIEL.ToString().Equals(searchTerm))
select pp).ToList();
Is there a way to make this work with a single query?
Thanks in advance :-)

sub linq query is making this take a very long time, how can I make this faster?

I have a list of employees that I build like this:
var employees = db.employees.Where(e => e.isActive == true).ToList();
var latestSales = from es in db.employee_sales.Where(x => x.returned == false);
Now what I want is a result like this:
int employeeId
List<DateTime> lastSaleDates
So I tried this, but the query takes a very very long time to finish:
var result =
(from e in employees
select new EmployeeDetails
{
EmployeeId = e.employeeId,
LastSaleDates =
(from lsd in latestSales.Where(x => x.EmployeeId == e.EmployeeId)
.Select(x => x.SaleDate)
select lsd).ToList()
};
The above works, but literally takes 1 minute to finish.
What is a more effecient way to do this?
You can use join to get all data in single query
var result = from e in db.employees.Where(x => x.isActive)
join es in db.employee_sales.Where(x => x.returned)
on e.EmployeeId equals es.EmployeeId into g
select new {
EmployeeId = e.employeeId,
LastSaleDates = g.Select(x => x.SaleDate)
};
Unfortunately you can't use ToList() method with Linq to Entities. So either map anonymous objects manually to your EmployeeDetails or change LastSalesDates type to IEnumerable<DateTime>.
Your calls to ToList are pulling things into memory. You should opt to build up a Linq expression instead of pulling an entire query into memory. In your second query, you are issuing a new query for each employee, since your are then operating in the Linq-to-objects domain (as opposed to in the EF). Try removing your calls to ToList.
You should also look into using Foreign Key Association Properties to makes this query a lot nicer. Association properties are some of the most powerful and useful parts of EF. Read more about them here. If you have the proper association properties, your query can look as nice as this:
var result = from e in employees
select new EmployeeDetails
{
EmployeeId = e.employeeId,
LastSaleDates = e.AssociatedSales
}
You might also consider using a join instead. Read about Linq's Join method here.
Is there an association in your model between employees and latestSales? Have you checked SQL Profiler or other profiling tools to see the SQL that's generated? Make sure the ToList() isn't issuing a separate query for each employee.
If you can live with a result structure as IEnumerable<EmployeeId, IEnumerable<DateTime>>, you could consider modifying this to be:
var result = (from e in employees
select new EmployeeDetails
{
EmployeeId = e.employeeId,
LastSaleDates = (from lsd in latestSales
where e.employeeId equals lsd.EmployeeId
select lsd.SaleDate)
};
I have some more general recommendations at http://www.thinqlinq.com/Post.aspx/Title/LINQ-to-Database-Performance-hints to help track issues down.

LINQ to SQL big query tuple

I didn't plan project correctly from beginning so I ended up having many SQL Queries scatterd around page.
So to avoid catching Sql exceptions in every other method or missing half of page due to exception in one of methods I want to pull all the data I need so :
This is method from dll file, I need to return it so I can work with objects after context dispose but I am doing something awfully wrong (first query finalproduct returns empty):
public static Tuple<Product, IEnumerable<Tag>, IEnumerable<ProductComment>, IEnumerable<ProductVote>, IEnumerable<Review>, IEnumerable<ReviewComment>, IEnumerable<ReviewVote>> GetBigProduct(int productID)
{
using (ProductClassesDataContext context = new ProductClassesDataContext())
{
var outproduct = from product in context.Products
where product.ID == productID
join producttag in context.ProductTags on product.ID equals producttag.productID
join tag in context.Tags on producttag.TagID equals tag.ID
join productComment in context.ProductComments on product.ID equals productComment.productID
join productVote in context.ProductVotes on product.ID equals productVote.productID
join review in context.Reviews on product.ID equals review.productID
join reviewComment in context.ReviewComments on review.ID equals reviewComment.reviewID
join reviewVote in context.ReviewVotes on review.ID equals reviewVote.reviewID
select new Tuple<Product, Tag, ProductComment, ProductVote, Review, ReviewComment, ReviewVote>
(product, tag, productComment, productVote, review, reviewComment, reviewVote);
var finalProduct = (from t in outproduct select t.Item1).Single();
var finalTags = (from t in outproduct select t.Item2).ToList();
var finalProductComments = (from t in outproduct select t.Item3).ToList();
var finalProductVotes = (from t in outproduct select t.Item4).ToList();
var finalReviews = (from t in outproduct select t.Item5).ToList();
var finalReviewsComments = (from t in outproduct select t.Item6).ToList();
var finalReviewsVotes = (from t in outproduct select t.Item7).ToList();
return new Tuple<Product, IEnumerable<Tag>, IEnumerable<ProductComment>, IEnumerable<ProductVote>, IEnumerable<Review>, IEnumerable<ReviewComment>, IEnumerable<ReviewVote>>
(finalProduct, finalTags, finalProductComments, finalProductVotes, finalReviews, finalReviewsComments, finalReviewsVotes);
}
}
Are you sure there is only one element returned by from t in outproduct select t.Item1 query?
According to documentation, Single():
Returns the only element of a sequence, and throws an exception if
there is not exactly one element in the sequence.
You may need First() or FirstOrDefault() instead.
Update after comments
I think the problem is with your joins. Actually, I don't think you want to do joins here. What you need, I guess, is something like following:
var outproduct = from product in context.Products
where product.ID == productID
select new Tuple<Product, ProductComment, Tag>(
product, // product itself
context.ProductComments.Where(p => p.productID == product.ID).ToList(), // list of comments for this product
(from pt in context.ProductTags
join tag in context.Tags on pt.TagID equals tag.ID
where pt.productID = product.ID
select tag).ToList() // list of tags for this product
// ... more dimensions in tuple
);
Then you will get, out of outproduct query, a collection of of tuples containing <Product, List of ProductComments, List of Tags>.
You can easily add rest of your tuple dimensions. I removed them for simplicity.
You can now returns this tuple directly from your function doing just
return outproduct.FirstOfDefault()
There is no need for your var finalProduct =-style lines and rebuilding the final tuple.
If you want to find the source of the problem,
start with the query without any joins, comment out the results lines except for the first line
var outproduct = from product in context.Products
where product.ID == productID
select new Tuple<Product>(product);
if this gives you some results, add the next join and try again. keep on adding joins, one by one, until your query fails. then you should have and idea where the problem is.
If you use FirstOrDefault() instead of Single() that wouldn't return null.
Hope it helps.

Categories