LINQ MVC ViewModel: Multiple joins to the same table with optional field - c#

Given the following database structure
Category
ID
CategoryNameResID
ParentCategory(Optional)
Resources
ID
Text
Lang
And given a ViewModel
public class CategoryViewModel
{
public int ID { get; set; }
public int CategoryNameResID { get; set; }
public string CategoryName { get; set; }
public int ParentCategory { get; set; }
public string ParentCategoryName { get; set; }
}
I want to get the list of all Categories with ParentCategoryName included
What I did so far is:
var categories = (from cat in db.Categories
join res in db.Resources on cat.CategoryNameResID equal res.ID
select new CategoryViewModel{
ID = cat.ID,
CategoryNameResID = cat.CategoryNameResID,
CategoryName = res.Text,
ParentCategory = cat.ParentCategory,
ParentCategoryName = (from p in db.Resources
where p.ID == cat.ParentCategory
select p.Text)
}).ToList();
I can't figure out how to get the ParentCategoryName without having to iterate again, which is definitely wrong.

Try this:
(from cat in cats
join res in resources on cat.ResId equals res.Id let categoryName = res.Text
join cat1 in cats on cat.ParentId equals cat1.Id into parentJoin
from pj in parentJoin.DefaultIfEmpty() let parentCatResId =pj==null?0: pj.ResId
join res1 in resources on parentCatResId equals res1.Id into resJoin
from res2 in resJoin.DefaultIfEmpty() let parentName = (res2==null?string.Empty:res2.Text)
select new CategoryVM
{
Id = cat.Id,
ResId = cat.ResId,
CatName = categoryName,
ParentId = cat.ParentId,
ParentName = parentName
}).ToList();

Say you have the following data with your tables
dbo.Categories
ID CategoryNameResID ParentCategory
1 1 NULL
2 2 NULL
3 3 1
4 4 NULL
5 5 4
6 6 4
7 7 4
dbo.Resources
ID Text Lang
1 Standard en-GB
2 Custom en-GB
3 Standard Oversize en-GB
4 Loose en-GB
5 Loose 2F Set en-GB
6 Loose (4” Scale) en-GB
7 Loose (6” Scale) en-GB
The following LINQ statements will output the desired results:
public class CategoryViewModel
{
public int ID { get; set; }
public int CategoryNameResID { get; set; }
public string CategoryName { get; set; }
public int? ParentCategory { get; set; }
public string ParentCategoryName { get; set; }
}
var categories = (from cat in Categories
join res in Resources on cat.CategoryNameResID equals res.ID let categoryName = res.Text
select new CategoryViewModel
{
ID = cat.ID,
CategoryNameResID = cat.CategoryNameResID,
CategoryName = categoryName,
ParentCategory = cat.ParentCategory,
ParentCategoryName = Resources.FirstOrDefault(r => r.ID == cat.ParentCategory).Text
}).ToList();
foreach(var c in categories)
{
Console.WriteLine(c.CategoryName + " - " + c.ParentCategoryName);
}
// Prints
Standard -
Custom -
Standard Oversize - Standard
Loose -
Loose 2F Set - Loose
Loose (4” Scale) - Loose
Loose (6” Scale) - Loose

Related

How to use a JSON array DB column in EF queries to perform filtering

Given the 'Product' Table:
[Table("products")]
public class Product: BaseModel
{
[Required]
[Column("Title", TypeName = "varchar(40)")]
[Display(Name = "Title")]
public string Title { get; set; }
[Required]
[Column("Description", TypeName = "varchar(50)")]
[Display(Name = "Description")]
public string Description { get; set; }
public string _Tags { get; set; } //<<<<<<< **Json Array**
[NotMapped]
public string[] Tags
{
get => _Tags == null ? Array.Empty<string>() : JsonConvert.DeserializeObject<string[](_Tags);
set => _Tags = JsonConvert.SerializeObject(value);
}
}
And the 'Product scores' table:
[Table("product_scores")]
public class ProductScore: BaseModel
{
[Required]
[Column("TagId", TypeName = "varchar(30)")]
[Display(Name = "TagId")]
public string Tag { get; set; }
[Required]
[Column("Score")]
[Display(Name = "Score")]
public long Score { get; set; }
}
And using DBContext from Microsoft.EntityFrameworkCore, I'm trying to do the following operation from a ProductService business logic:
Retrieve all Products from the store that are not historized ( third table)
Sort the products list based on the AVERAGE sum of the 'Score' per 'Tag' list
Example:
Products:
Title
Description
Tags
T-Shirt
A beautiful T-Shirt
["fashion","comic","pink"]
Jeans
Strong Jeans
["fashion","strong"]
Product Scores:
TagId
Score
fashion
15
comic
-2
pink
4
strong
20
The math should be for the product "T-shirt" = (15 -2 + 4) / 3 = 5,66
for product "Jeans" = 15 + 20 = 35
Sorting by those value should retrieve list of products (DESC):
Jeans
T-Shirt
This is my starting point:
_dbContext.Products
.Where(p =>
!_dbContext.HistorizedProducts.Any(hp=> hp.ProductId == p.Id && hp.UserId == CurrentUserId)
&& p.UserId != CurrentUserId
) //<<< HERE I SHOULD "APPEND" the JOIN and the FILTERING;
The requirements needs it to be done on the DB level since we're applying Pagination.
I'm pretty new to C# programming, any help is very welcome.

How to get data from two tables and save it in DTO in Net Core Entity Framework?

I just explain this in example, say I have two tables, field and modulefield
field
id name ismodule
1 Firstname 1
2 Lastname 0
modulefield
id moduleid fieldid
1 22 1
That is a simple representation of my database. Now in EF, how can I get a row with these columns
modulefield.moduleid field.id field.name
22 1 Firstname
Basically, I just want to get the modulefield.moduleid if the field.ismodule is true.
The equivalent sql script I think for this is
SELECT b.submoduleid, a.* FROM field a, modulefield b;
My current code is this(snippet)
var result = new List<FieldDTO>();
var fields = await _repo.GetAsync<Field>();
result = _mapper.Map<List<FieldDTO>>(fields);
You could use linq join to return a List<FieldDTO> directly.
var result = (from e in _context.Fields
join mf in _context.ModuleFields on e.Id equals mf.FieldId
where e.IsModule == 1
select new FieldDTO
{
ModulefieldModuleid = mf.ModuleId,
FieldId = e.Id,
FieldName=e.Name
}).ToList();
I assume there could be more then one Module per Field.
Query:
SELECT mf.submoduleid, f.id, f.name
FROM field AS f, modulefield AS mf
WHERE f.ismodule = 1 AND
f.id = mf.fieldid
EF Linq expression:
List<FieldDTO> result = _context.Fields
.Include("ModuleField")
.Where(f => f.ismodule == true)
.Select(f => new FieldDTO(){
ModuleFieldModuleId = f.ModuleField.moduleid,
FieldId = f.id,
FieldName = f.Name})
.ToList();
ModuleField is navigation property in Field class.
EDIT:
Models:
public class Field
{
[Key]
public long Id { get; set; }
public string FieldName { get; set; }
[NotMapped]
public bool IsModule
{
get
{
return this.Module != null;
}
}
public virtual Module Module {get; set;}
}
public class Module
{
[Key]
public long Id { get; set; }
public string ModuleName { get; set; }
public virtual IEnumerable<Field> Fields { get; set }
}
On model creating:
modelBuilder.Entity<Field>()
.HasOptional(s => s.Module); // Marking property optional
Getting the Module.ModuleId if IsModule of Field is true:
List<FieldDTO> result = _context.Fields
.Include("Module")
.Where(f => f.IsModule == true)
.Select(f => new FieldDTO(){
ModuleFieldModuleId = f.Module.Id,
FieldId = f.Id,
FieldName = f.Name})
.ToList();

Build bidimensional table based on many-to-many EF relationship

I want to build a bi-dimensional table based on a Driver/Race/Points relationships.
Models:
public class Race
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public DateTime RaceStart { get; set; }
public Circuit Circuit { get; set; }
public virtual ICollection<DriverRacePoint> DriverRacePoints { get; set; }
}
public class Driver
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Standing { get; set; }
public virtual ICollection<DriverRacePoint> DriverRacePoints { get; set; }
}
public class DriverRacePoint
{
[Key, Column("Driver_Id", Order = 0)]
public int Driver_Id { get; set; }
[Key, Column("Race_Id", Order = 1)]
public int Race_Id { get; set; }
[ForeignKey("Driver_Id")]
public virtual Driver Driver { get; set; }
[ForeignKey("Race_Id")]
public virtual Race Race { get; set; }
public string Points { get; set; }
}
The result view:
| Race1 | Race2 | Race3 | Race4 |
Driver1 | points | points | points | points | total points
Driver2 | points | points | points | points | total points
Driver3 | points | points | points | points | total points
The lines order is not by the total but by the Driver.Standing
How do I build the ViewModel from the DbContext to get the result view?
Here is an example of the result I need:
http://en.espnf1.com/f1/motorsport/season/138549.html?template=standings
I don't understand why the Points property in the DriverRacePoint entity is string, but if you change that property for a numeric type, for example int, you could do this to obtain the info that you need:
using (var db = new YourDbContext())
{
var query = (from driver in db.Drivers
join drp in db.DriverRacePoints on driver.Id equals drp.Driver_Id
join race in db.Races on drp.Race_Id equals race.Id
where race.RaceStart.Year==2014 //races for an specific year
group new {driver,drp, race} by driver.Id into g
select new {DriverId=g.Key,// The driver Id
RacesWithPoints=g.Select(a=>new {a.race,a.drp.Points}),// All the races ( and the points X race) where the driver participates
TotalPoints=g.Sum(a=>a.drp.Points)// Total of points
}).OrderByDescending(e=>e.TotalPoints);
//use the data that you get from this query
}
Update 1
If you need the driver info then you can modify the query this way:
var query = (from driver in db.Drivers
join drp in db.DriverRacePoints on driver.Id equals drp.Driver_Id
join race in db.Races on drp.Race_Id equals race.Id
where race.RaceStart.Year==2014 //races for an specific year
group new {driver,drp, race} by driver.Id into g
select new { DriverName = g.FirstOrDefault().driver.Name,
DriverUrlSlug = g.FirstOrDefault().driver.UrlSlug,
// All the races ( and the points X race) where the driver participates
RacesWithPoints = g.Select(a => new StandingRacePointsVM { RacePoints = a.drp.Points == null ? -1 : a.drp.Points }).ToList(),
TotalPoints = g.Sum(a => a.drp.Points),
Standing = g.FirstOrDefault().driver.Standing
}).OrderBy(d => d.Standing).ToList();
For the list of races in the header, how do suggest that I build it?
Well, the problem is you don't know if some driver was involved in all the races. So I think you need to do another query for that:
var query2 = from race in db.Races
where race.RaceStart.Year == 2014
select new {race.Id, race.Name};
Update 2
I think this query is better for you but I can't test it because I don't have your model and data. This query will get you all the drivers, and for each driver you will get the list of all the races, including where he was not involved:
var query3 = (from driver in db.Drivers
join drp in db.DriverRacePoints on driver.Id equals drp.Driver_Id
from race in db.Races
where race.RaceStart.Year == 2014
select new { driver,drp, race })
.GroupBy(e => e.driver.Id)
.Select(g => new
{
DriverName = g.FirstOrDefault().driver.Name,
RacesWithPoints = g.Select(a => new { a.race.Name, Points = a.drp.Race_Id == a.race.Id ? 0 : a.drp.Points }), // All the races (if the driver was involved => you have the Points value, otherwise, the value is 0 )
TotalPoints = g.Sum(a => a.drp.Points)// Total of points
}).OrderByDescending(e=>e.TotalPoints);
Now to create the header, you can choose the first driver an iterate foreach race using the Name.

Add list of items using linq

I have a class that holds Categories.
public class CategoryDto
{
public int Id { get; set; }
public int PortfolioId { get; set; }
public string Description { get; set; }
public List<SubCategoryDto> SubCategories { get; set; }
public CategoryDto()
{
SubCategories = new List<SubCategoryDto>();
}
}
It has a List in it, which is a list of SubCategory classes:
public class SubCategoryDto
{
public int Id { get; set; }
public int CategoryId { get; set; }
public CategoryDto Category { get; set; }
public string Description { get; set; }
}
I then populate this item, but I am getting a list bases on a 'PortfolioId'.
var cats = (from p in Context.transaction_category
where p.account_portfolio_id == portfolioId
&& p.deleted == null
select new CategoryDto
{
Id = p.id,
Description = p.description,
PortfolioId = p.account_portfolio_id
}).ToList();
The Categories table has a foreign key to SubCategories. Each category has 0:n sub categories. So, the entity framework model has a Context.transaction_category.transaction_sub_categories collection.
So now what I do is, foreach through the categories in the list above, and populate the sub categories.
Is there a way to do this in the same link statement? The Categories object has a List list. Can it be done in the above Linq statement?
Edit:
This is the fix attempt, as recommended, but is presenting an error:
var cats = (from p in Context.transaction_category
where p.account_portfolio_id == portfolioId
&& p.deleted == null
select new CategoryDto
{
Id = p.id,
Description = p.description,
PortfolioId = p.account_portfolio_id,
SubCategories = (from s in Context.transaction_sub_category where s.transaction_category_id == p.id
&& s.deleted == null
select new SubCategoryDto
{
Id = s.id,
Description = s.description,
CategoryId = s.transaction_category_id
}).ToList()
}).ToList();
LINQ to Entities does not recognize the method
'System.Collections.Generic.List1[Objects.SubCategoryDto]
ToList[SubCategoryDto](System.Collections.Generic.IEnumerable1[Objects.SubCateg‌​oryDto])'
method, and this method cannot be translated into a store expression.
You can do it like this:
var cats = (from p in Context.transaction_category
where p.account_portfolio_id == portfolioId
&& p.deleted == null
select new CategoryDto
{
Id = p.id,
Description = p.description,
PortfolioId = p.account_portfolio_id,
SubCategories = (from s in Context.transaction_category.transaction_sub_categories
where s.CategoryId == p.Id
select new SubCategoryDto {
Id = s.Id,
Description = s.Decription
}).ToList()
}).ToList();
Update: To make it easier change your SubCategories and Category properties like this:
public virtual List<SubCategoryDto> SubCategories { get; set; }
public virtual CategoryDto Category { get; set; }
Then you can use include and simply load your sub categories like this:
var cats = Context.transaction_category
.Where(p => p.account_portfolio_id == portfolioId && p.deleted == null)
.Include(p => p.SubCategories);

EFv1 mapping 1 to many Relationship to POCOs

I'm trying to work through a problem where I'm mapping EF Entities to POCO which serve as DTO.
I have two tables within my database, say Products and Categories. A Product belongs to one category and one category may contain many Products. My EF entities are named efProduct and efCategory. Within each entity there is the proper Navigation Property between efProduct and efCategory.
My Poco objects are simple
public class Product
{
public string Name { get; set; }
public int ID { get; set; }
public double Price { get; set; }
public Category ProductType { get; set; }
}
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
public List<Product> products { get; set; }
}
To get a list of products I am able to do something like
public IQueryable<Product> GetProducts()
{
return from p in ctx.Products
select new Product
{
ID = p.ID,
Name = p.Name,
Price = p.Price
ProductType = p.Category
};
}
However there is a type mismatch error because p.Category is of type efCategory. How can I resolve this? That is, how can I convert p.Category to type Category?
Likewise when I do
return from c in ctx.Categories
where c.ID == id
select new Category
{
ID = c.ID,
Name = c.Name,
ProductList = c.Products;
};
I get a mismatch because ProductList is of type Product, where c.Products is an EntityCollection
I know in .NET EF has added support for POCO, but I'm forced to use .NET 3.5 SP1.
return from p in ctx.Products
select new Product
{
ID = p.ID,
Name = p.Name,
Price = p.Price
ProductType = new Category
{
ID = p.Category.ID,
Name = p.Category.Name // etc.
}
};
For Category you do:
return from c in ctx.Categories
where c.ID == id
select new Category
{
ID = c.ID,
Name = c.Name,
ProductList = from p in c.Products
select new Product
{
ID = p.ID,
Name = p.Name,
Price = p.Price
}
};

Categories

Resources