Accessing Included object - c#

im working with a new ASP.NET MVC3 project and it seems like im missing something in my LINQ skills.
Im formatting data for a Json "call" to be used my a jqGrid.
It works fine but now i want to add a related object by a Linq .Include() expression.
Think i better show with code.
var query = db.Products.Include("Category");
var jsonData = new
{
total = 1, // calc
page = page,
records = db.Products.Count(),
rows = query.Select(x => new { x.Id, x.Name, x.PartNr })
.ToList()
.Select(x => new {
id = x.Id,
cell = new string[] {
x.Id.ToString(),
x.Name.ToString(),
x.PartNr.ToString(),
//x.Category.Name.ToString()
//This does not work but object is there.
}}).ToArray(),
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
Problem area => //x.Category.Name.ToString()
The strange thing here is if i Break and watch the query (//x.Category.Name.ToString()) i can actually find the attached Category object but how, if possible, can i use it in my ano method?

The problem is that you are first selecting an anonymous object with the properties Id, Name and PartNr. Then you execute this query against the database (with ToList()) and then you do a new select on the list of anonymous objects and try to access a property that's not in your anonymous object.
You should include the category in your anonymous object so you can access it in the second select. Or you should select the final structure with the first select query so it will be executed against your database.
This would work for example:
rows = query.Select(x => new { x.Id, x.Name, x.PartNr, x.Category })
.ToList()
.Select(x => new
{
id = x.Id,
cell = new string[] {
x.Id.ToString(),
x.Name.ToString(),
x.PartNr.ToString(),
x.Category.Name.ToString()
}
}).ToArray()
Or you simplify your query to only one and execute against the database:
rows = query.Select(x => new
{
x.Id,
cell = new string[]
{
x.Id.ToString(),
x.Name.ToString(),
x.PartNr.ToString(),
x.Category.Name.ToString()
}
}).ToArray()

Related

Filtering nested list is not bringing back the correct result set

I currently have a list of skills which looks something like:
var list = new List<Test>
{
new Test
{
Name = "Some Name",
Skills = new List<Skill>
{
new Skill { SkillName = "Java" },
new Skill { SkillName = "JavaScript" },
new Skill { SkillName = "C#" },
new Skill { SkillName = "CSS" }
}
}
};
Now, I am trying to filter the result by the search term Java for which the code looks something like:
var searchTerm = "Java";
var filteredList = list.Where(t => t.Skills.Any(s => s.SkillName.Contains(searchTerm))).ToList();
Here, I would expect the filteredList to contain Java & JavaScript but it's bringing back all the 4 items. Does anyone know where I'm going wrong?
dotnetfiddle
You need to create a new list to achieve this. You could do this as follows:
var filteredList =
// where filters down the list to the tests you are interested in(ones containing a skill that has the search term)
list.Where(t => t.Skills.Any(s => s.SkillName.Contains(searchTerm)))
// select creates a new list, where each test only has the skills that match the search term
.Select(t => new Test
{
Name = t.Name,
Skills = t.Skills.Where(s => s.SkillName.Contains(searchTerm)).ToList()
})
.ToList();
In your query Where operator applies to the list of tests only.
You need something like:
var searchTerm = "Java";
var filteredList = list
.Where(t => t.Skills.Any(s => s.SkillName.Contains(searchTerm)))
.Select(t => new Test
{
Name = t.Name,
Skills = t.Skills
.Where(s => s.SkillName.Contains(searchTerm))
.ToList()
})
.ToList();
Your query is about retrieving the Test objects where the Skills property contains an item where the skillname contains your search term. Since you return the full Test object, you're seeing the unaltered list.
You need to reassign the nested Skills property to a list with just the terms you looked for.

How do I include attribute in Entity Framework Group By result

Let's say I have a table of locations with location ID and location name. And let's say I want to get the revenues for each location (in this simple scenario I might not even need GroupBy - but please assume that I do!)
var revenues = await _context.SaleTransaction.GroupBy(s => s.LocationId)
.Select(x => new LocationDTO {
LocationId = x.Key,
LocationName = ???
Revenues = x.Sum(i => i.Amount)
}).ToListAsync();
I tried to cheat
LocationName = x.Select(i => i.Location.LocationName).First()
since all location names for this ID are the same. But EF can't translate First() unless I use AsEnumerable() and bring the whole sales table into application memory.
Or I can traverse the result the second time:
foreach(var revenue in revenues) {
revenue.LocationName = _context.Location.Find(revenue.LocationId).LocationName;
}
Given that the number of locations is fixed (and relatively small), it may be the best approach. Still, neither going to DB for every location O(n) nor pulling the whole location list into memory doesn't sit well. Maybe there is a way to assign LocationName (and some other attributes) as part of GroupBy statement.
I am using EF Core 5; or if something is coming in EF Core 6 - that would work as well.
From what I can briefly see is that you need a linq join query in order to join the searches. With EF linq query it means those won't be loaded into memory until they are used so it would solve the problem with loading the whole table.
You could write something like:
var revenues = await _context.SaleTransactions.Join(_context.Locations, s => s.LocationId, l => l.Id, (s, l) => new {LocationId = s.LocationId, LocationName = l.LocationName, Revenues = s.Sum(i => i.Amount)});
I will link the whole fiddle with the mock of your possible model
https://dotnetfiddle.net/BGJmjj
You can group by more than one value. eg;
var revenues = await _context.SaleTransaction
.GroupBy(s => new {
s.LocationId,
s.Location.Name
})
.Select(x => new LocationDTO {
LocationId = x.Key.LocationId,
LocationName = x.Key.Name,
Revenues = x.Sum(i => i.Amount)
}).ToListAsync();
Though it seems like you are calculating a total per location, in which case you can build your query around locations instead.
var revenues = await _context.Location
.Select(x => new LocationDTO {
LocationId = x.Id,
LocationName = x.Name,
Revenues = x.SaleTransactions.Sum(i => i.Amount)
}).ToListAsync();
var revenues = await _context.Location
.Select(x => new LocationDTO {
LocationId = x.Id,
LocationName = x.Name,
Revenues = x.SaleTransactions.Sum(i => i.Amount)
}).ToListAsync();
there is example:
.NetFiddle

How can I reuse a subquery inside a select expression?

In my database I have two tables Organizations and OrganizationMembers, with a 1:N relationship.
I want to express a query that returns each organization with the first and last name of the first organization owner.
My current select expression works, but it's neither efficient nor does it look right to me, since every subquery gets defined multiple times.
await dbContext.Organizations
.AsNoTracking()
.Select(x =>
{
return new OrganizationListItem
{
Id = x.Id,
Name = x.Name,
OwnerFirstName = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).FirstName,
OwnerLastName = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).LastName,
OwnerEmailAddress = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).EmailAddress
};
})
.ToArrayAsync();
Is it somehow possible to summarize or reuse the subqueries, so I don't need to define them multiple times?
Note that I've already tried storing the subquery result in a variable. This doesn't work, because it requires converting the expression into a statement body, which results in a compiler error.
The subquery can be reused by introducing intermediate projection (Select), which is the equivalent of let operator in the query syntax.
For instance:
dbContext.Organizations.AsNoTracking()
// intermediate projection
.Select(x => new
{
Organization = x,
Owner = x.Members
.Where(member => member.Role == RoleType.Owner)
.OrderBy(member => member.CreatedAt)
.FirstOrDefault()
})
// final projection
.Select(x => new OrganizationListItem
{
Id = x.Organization.Id,
Name = x.Organization.Name,
OwnerFirstName = Owner.FirstName,
OwnerLastName = Owner.LastName,
OwnerEmailAddress = Owner.EmailAddress
})
Note that in pre EF Core 3.0 you have to use FirstOrDefault instead of First if you want to avoid client evaluation.
Also this does not make the generated SQL query better/faster - it still contains separate inline subquery for each property included in the final select. Hence will improve readability, but not the efficiency.
That's why it's usually better to project nested object into unflattened DTO property, i.e. instead of OwnerFirstName, OwnerLastName, OwnerEmailAddress have a class with properties FirstName, LastName, EmailAddress and property let say Owner of that type in OrganizationListItem (similar to entity with reference navigation property). This way you will be able to use something like
dbContext.Organizations.AsNoTracking()
.Select(x => new
{
Id = x.Organization.Id,
Name = x.Organization.Name,
Owner = x.Members
.Where(member => member.Role == RoleType.Owner)
.OrderBy(member => member.CreatedAt)
.Select(member => new OwnerInfo // the new class
{
FirstName = member.FirstName,
LastName = member.LastName,
EmailAddress = member.EmailAddress
})
.FirstOrDefault()
})
Unfortunately in pre 3.0 versions EF Core will generate N + 1 SQL queries for this LINQ query, but in 3.0+ it will generate a single and quite efficient SQL query.
How about this:
await dbContext.Organizations
.AsNoTracking()
.Select(x =>
{
var firstMember = x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner);
return new OrganizationListItem
{
Id = x.Id,
Name = x.Name,
OwnerFirstName = firstMember.FirstName,
OwnerLastName = firstMember.LastName,
OwnerEmailAddress = firstMember.EmailAddress
};
})
.ToArrayAsync();
How about doing this like
await dbContext.Organizations
.AsNoTracking()
.Select(x => new OrganizationListItem
{
Id = x.Id,
Name = x.Name,
OwnerFirstName = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner).FirstName,
OwnerLastName = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner)).LastName,
OwnerEmailAddress = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner)).EmailAddress
})
.ToArrayAsync();

Linq Query join a table to a List<string> object to find any Strings not in table

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.

Retain default order for Linq Contains

I want to retain the default order that comes from sql, after processing by Linq also.I know this question has been asked before. Here is a link Linq Where Contains ... Keep default order.
But still i couldn't apply it to my linq query correctly. could anyone pls help me with this? Thanks!
Here is the query
var x = db.ItemTemplates.Where(a => a.MainGroupId == mnId)
.Where(a => a.SubGruopId == sbId)
.FirstOrDefault();
var ids = new List<int> { x.Atribute1, x.Atribute2, x.Atribute3, x.Atribute4 };
var y = db.Atributes.Where(a => ids.Contains(a.AtributeId))
.Select(g => new
{
Name = g.AtributeName,
AtType = g.AtributeType,
Options = g.atributeDetails
.Where(w=>w.AtributeDetailId!=null)
.Select(z => new
{
Value=z.AtributeDetailId,
Text=z.AtDetailVal
})
});
Your assumption is wrong. SQL server is the one that is sending the results back in the order you are getting them. However, you can fix that:
var x = db.ItemTemplates.Where(a => a.MainGroupId == mnId)
.Where(a => a.SubGruopId == sbId)
.FirstOrDefault();
var ids = new List<int> { x.Atribute1, x.Atribute2, x.Atribute3, x.Atribute4 };
var y = db.Atributes.Where(a => ids.Contains(a.AtributeId))
.Select(g => new
{
Id = g.AtributeId,
Name = g.AtributeName,
AtType = g.AtributeType,
Options = g.atributeDetails
.Where(w=>w.AtributeDetailId!=null)
.Select(z => new
{
Value=z.AtributeDetailId,
Text=z.AtDetailVal
})
})
.ToList()
.OrderBy(z=>ids.IndexOf(z.Id));
Feel free to do another select after the orderby to create a new anonymous object without the Id if you absolutely need it to not contain the id.
PS. You might want to correct the spelling of Attribute, and you should be consistent in if you are going to prefix your property names, and how you do so. Your table prefixes everything with Atribute(sp?), and then when you go and cast into your anonymous object, you remove the prefix on all the properties except AtributeType, which you prefix with At. Pick one and stick with it, choose AtName, AtType, AtOptions or Name, Type, Options.

Categories