ASP.NET MVC - Join Tables using LINQ - linq

My application is structured as:
namespace DomainModel.Abstract
{
public interface IContentItemsRepository
{
IQueryable<Content> ContentItems { get; }
IQueryable<Category> Categories { get; }
IQueryable<ContentCategory> ContentCategories { get; }
}
}
namespace DomainModel.Entities
{
[Table(Name = "Content")]
public class Content
{
[Column(IsPrimaryKey = true,
IsDbGenerated = true,
AutoSync = AutoSync.OnInsert)]
public int content_id { get; set; }
[Column]
public string type { get; set; } // article, video, recipe, tool
[Column]
public string title { get; set; }
...
namespace DomainModel.Entities
{
[Table(Name = "Content_Categories")]
public class ContentCategory
{
[Column(IsPrimaryKey = true,
IsDbGenerated = true,
AutoSync = AutoSync.OnInsert)]
public int content_category_id { get; set; }
[Column]
public int content_id { get; set; }
[Column]
public int category_id { get; set; }
...
namespace DomainModel.Entities
{
[Table(Name = "Categories")]
public class Category
{
[Column(IsPrimaryKey = true,
IsDbGenerated = true,
AutoSync = AutoSync.OnInsert)]
public int category_id { get; set; }
[Column]
public string category_name { get; set; }
[Column]
public string type { get; set; } //recipe,tool,other
[Column]
public int ordering { get; set; }
...
I can do this:
var articlesInCategory = _contentRepository.ContentItems
.Where(x => x.type == "article");
and get a list of articles. No problem.
However, I now need to select Content based on categories. So, I need to join Content to ContentCategory to Category.
I have no idea how to do this. Any help will be much appreciated.
Thanks.
EDIT:
I think part of my problem is that I don't even know how to call what I'm doing, so it's hard to search for this. Am I even doing LINQ to SQL, or LINQ to Entities, or is it LINQ to Objects?

The join query will be something like this.
var content=
from category in _contentRepository.Category
join contentCategory in _contentRepository.ContentCategory
on category.category_id equals contentCategory.category_id
join content in _contentRepository.Content
on content.content_id equals contentCategory.content_id
where category.category_id==#yourcategoryId
select new {type , title }

The concept you are looking for is called SelectMany in linq, and there are a number of ways to accomplish it.
One is:
var content =
from category in _categoryRepository.CategoryItems
join contCat in _contentCategoryRepository.Items
on category.category_id == conCat.category_id
where category.category_id == parameter
select contCat.content_id;
From here you should be able to extend it into pulling out all the data you need...look into the into keyword and check out this link if you haven't already.

Related

How to join 3 tables in linq and get some not included fields in query

I'm implementing asp.net core project. I have 3 tables Apiapp, ApiAppHistory and EntityType. There are three fields with the names SentType, Status and Reason in ApiAppHistory and those fields are of kind Id (int type) in APIApphistory. I joined ApiApp and ApiAppHistory tables in order to get those three fields from ApiAppHistory but because they are of kind int and are unclear when showing the result to the user, I join them with EntityType table which has their related name. In the select part of my query, in addition to ApiApp fields I also need to have SentType, Status and Reason value fields.
Here below is my incomplete query:
var qq = _context.Apiapp
.Include(a => a.Api)
.Include(a => a.Application)
.Include(a => a.Data);
var t12 = (from r in qq
from b in _context.ApiAppHistory
from s in _context.EntityType
where r.LastRequest== b.Id && b.SentType == s.Id
&& b.Reason == s.Id
&& b.Status == s.Id
select new { r, s.name for Reason, s.name for
SentType ,s.name for Status});
I want in select part of my query, obtain name of the fields that I specified from the EntityType table. However, I don't know how to do it. I appreciate if someone helps me.
Here is my EntityType table:
Here are my APIAppHistory and EntityType class model:
public partial class ApiAppHistory
{
public int Id { get; set; }
public int? SentType { get; set; }
public int? Reason { get; set; }
public int? Status { get; set; }
public virtual Apiapp ApiApp { get; set; }
public virtual EntityType StatusNavigation { get; set; }
public virtual EntityType SentTypeNavigation { get; set; }
public virtual EntityType ReasonNavigation { get; set; }
}
public partial class EntityType
{
public EntityType()
{
ApiAppHistoryStatusNavigation = new HashSet<ApiAppHistory>();
ApiAppHistorySentTypeNavigation = new HashSet<ApiAppHistory>();
ApiAppHistoryReasonNavigation = new HashSet<ApiAppHistory>();
}
public int Id { get; set; }
public string Name { get; set; }
public string EntityKey { get; set; }
public virtual ICollection<ApiAppHistory> ApiAppHistoryStatusNavigation { get; set; }
public virtual ICollection<ApiAppHistory> ApiAppHistorySentTypeNavigation { get; set; }
public virtual ICollection<ApiAppHistory> ApiAppHistoryReasonNavigation { get; set; }
}
}

What would be the best way to get all the products in all the child categories of a selected main category?

What would be the best way to get all the products in all the child categories of a selected main category?
This is my Class File Structure:
public partial class Category
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentCategoryId { get; set; } //reference to Id
public ICollection<Category> _subcategories;
}
public partial class ProductCategory
{
public int Id { get; set; }
public int ProductId { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual Product Product { get; set; }
}
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductViewMap> _productViewmap;
}
public class ProductViewMap
{
public int ProductId { get; set; }
public int ProductViewCount { get; set; }//indicated how many times product has been viewed means most popular product.
public virtual Product Product { get; set; }
}
This is what i have tried:
//List to hold all Category Ids of Parent Category Id say for eg:1
List<int> categoryChildList = new List<int>();
var data = (from temp in context.Category
where temp.ParentCategoryId == parentCategoryId
select new { CategoryId = temp.Id });
if(data.Count() > 0)
{
foreach (var cat in data)
{
int _cat = Convert.ToInt32(cat.CategoryId);
categoryChildList.Add(_cat);
}
var tmpList = (from p in Context.ProductCategory
join m in context.Product on p.ProductId equals m.Id
join n in context.ProductViewMap on m.Id equals n.ProductId
where categoryChildList.Contains(p.CategoryId)
select m).ToList();
Here error is coming:
Object reference not set to instance of object.**
When i am removing this line then everything works fine:
join n in context.ProductViewMap on m.Id equals n.ProductId
any help would be greatly appreciated.
Sql fiddle which contain sample records:http://www.sqlfiddle.com/#!3/bde6b
If Input is :Computer(parentCategoryId:1) then output is as below
Final output:
ProductId ProductName
1 hp
2 compaq
3 lenovo
If all you're trying to do is to grab the Product records, then using the Extension Method syntax you could do it this way:
var products = context.ProductCategory.Where(pc => pc.Category.ParentCategoryID != null && pc.Category.ParentCategoryID == parentCategoryID)
.Select(pc => pc.Product)
.Distinct()
.ToList()
.OrderBy(p => p.ProductViewMap.Max(pvm => pvm.ProductViewCount);
All of the joins will be taken care of by the SQL query generated by LINQ to Entities.

Linq for objects

I have:
public class Basket
{
public int BasketId { get; set;}
public string Name { get; set;}
public virtual ICollection<BasketItem> BasketItems {get; set;}
}
public class BasketItem
{
public int BasketItemId { get; set;}
public string Name { get; set;}
public int BasketId { get; set;}
public int ProductId { get; set; }
public virtual Product product { get; set;}
}
public class Product
{
public int ProductId { get; set;}
public string ProductName { get; set; }
}
Can anyone know how to query in linq to retrieve BasketList which includes basketItems list and prodcut ?
Sorry: The question had confused. I'm trying retrieve basketList with this:
(from b in Baskets
join bi in BasketItems on b.Id equals bi.BasketId
join pp in Products on bi.ProductId equals pp.Id
where b.Id == 10001
select b).ToList()
Product is not included in basketItems with above linq query. I've updated the question.
You've set up your objects like a relational database with ProductId and BasketId as foreign keys onto a BasketItem "table". It is better to embed your "sub" classes into your container class. That makes using Linq to query much easier.
Try:
public class BasketItem
{
public int BasketItemId { get; set; }
public string Name { get; set; }
public Basket Basket { get; set; }
public Product Product { get; set; }
}
public class Basket
{
public int BasketId { get; set; }
public string Name { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
}
private void SetupAndQuery()
{
#region setup data objects
Product product01 = new Product {ProductName = "Whizzo", ProductId = 12345};
Basket basket01 = new Basket {Name = "Johns Basket", BasketId = 34535987};
Product product02 = new Product { ProductName = "Blammo", ProductId = 54321 };
Basket basket02 = new Basket { Name = "Susans Basket", BasketId = 23546758 };
List<BasketItem> basketItems = new List<BasketItem>();
BasketItem basketItem01 = new BasketItem();
basketItem01.BasketItemId = 12365;
basketItem01.Name = "Erm";
basketItem01.Basket = basket01;
basketItem01.Product = product01;
basketItems.Add(basketItem01);
BasketItem basketItem02 = new BasketItem();
basketItem02.BasketItemId = 15478;
basketItem02.Name = "What";
basketItem02.Basket = basket02;
basketItem02.Product = product02;
basketItems.Add(basketItem02);
#endregion
#region Query
//The 2 code blocks below do the same thing
//Foreach/If version of the Query
foreach (BasketItem item in basketItems)
{
if (item.Product.ProductId == 54321)
{
//Do stuff here
}
}
//LINQ version of the Query
foreach (BasketItem item in basketItems.Where(item => item.Product.ProductId == 54321))
{
//Do stuff here
}
#endregion
}
You may want to change what types go inside other types. I suggest Basket at the top and a Product list inside that. BasketItem looks unnecessary.

most efficient Entity Framework Code First method of flattening / projecting parent entity with specific child

I have a parent entity Widget with core members and multiple WidgetTranslation children that have language translated members i.e. Description text available in English, French, German etc.
e.g.
public class Widget
{
public int Id { get; set; }
public string Code { get; set; }
public virtual ICollection<WidgetTranslation> WidgetTranslations { get; set; }
}
public class WidgetTranslation
{
public int WidgetId { get; set; }
public virtual Widget Widget { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
What is the most efficient method of querying the widget collection, flattening for a given LanguageId & projecting to a TranslatedWidget DTO
public class TranslatedWidget
{
public int Id { get; set; }
public string Code { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
Given languageId I've started with
DbSet.Select(w => new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = w.LanguageId,
Name = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Name,
Description = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Description,
Summary = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Summary
});
But I've a feeling this is inefficient and won't scale for more properties on WidgetTranslation.
Thanks
Use SelectMany to flatten structures via a single join:
var widgetQuery = from w in dbSet.Widgets
from wt in w.WidgetTranslations
where wt.Language == languageId
select new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = w.LanguageId,
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
});
I'm assuming here that you only have a single translation for each widget in a given language.
I would move Name, Description and Summary into a nested class of your DTO...
public class TranslatedWidgetTranslation
{
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
public class TranslatedWidget
{
public int Id { get; set; }
public string Code { get; set; }
public int LanguageId { get; set; }
public TranslatedWidgetTranslation Translation { get; set; }
}
Then you can project into that class and need First only once which would result in only one TOP(1) subquery in SQL instead of three:
DbSet.Select(w => new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = languageId,
Translation = w.WidgetTranslations
.Where(wt => wt.LanguageId == languageId)
.Select(wt => new TranslatedWidgetTranslation
{
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
})
.FirstOrDefault()
});
You must use FirstOrDefault here, First is not supported in a LINQ-to-Entities projection.
If you don't want that nested type you can project into anonymous types first and then convert into your final class, but the code will be a bit longer:
DbSet.Select(w => new
{
Id = w.Id,
Code = w.Code,
LanguageId = languageId,
Translation = w.WidgetTranslations
.Where(wt => wt.LanguageId == languageId)
.Select(wt => new
{
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
})
.FirstOrDefault()
})
.AsEnumerable()
.Select(x => new TranslatedWidget
{
Id = x.Id,
Code = x.Code,
LanguageId = x.LanguageId,
Name = x.Translation != null ? x.Translation.Name : null,
Description = x.Translation != null ? x.Translation.Description : null,
Summary = x.Translation != null ? x.Translation.Summary : null
});

Present multiple IEnumberables and single value properties in single webgrid

I have an Inventory Class that contains not only its own fields but several reference IDs to other classes.
public class Inventory {
public int Id { get; set; }
public string RtNum { get; set; }
public string AcntNum { get; set; }
public string CardNum { get; set; }
public string Num { get; set; }
[Range(1,3)]
public int Type { get; set; }
public int CompanyId { get; set; }
public int BranchId { get; set; }
public int PersonId { get; set; } }
In my action I generate several IEnumerable lists of the relevant fields from the other classes. I also have several non-list values I want to pass to the View. I know how to create a ViewModel to pass everything to the webgrid but have no way of iterating through the lists. I also know how to AutoMap an index to one list, see How to display row number in MVC WebGrid.
How would you combine the two so that you could use the index to iterate through multiple lists?
Update #1 (more detail)
public class Company {
public int Id { get; set; }
public string Name { get; set; } }
public class Branch {
public int Id { get; set; }
public string Name { get; set; } }
public class Person {
public int Id { get; set; }
public string Name { get; set; } }
public class MyViewModel {
public int PageNumber { get; set; }
public int TotalRows { get; set; }
public int PageSize { get; set; }
public IEnumerable<Inventory> Inventories { get; set; }
public int Index { get; set; }
public IEnumerable<string> CmpNm { get; set; }
public IEnumerable<string> BrnNm { get; set; }
public IEnumerable<string> PrnNm { get; set; } }
Controller
public class InventoryController : Controller
{ // I have a paged gird who’s code is not relevant to this discussion but a pagenumber,
// pagesize and totalrows will be generated
private ProjectContext _db = new ProjectContext();
public ActionResult Index() {
IEnumerable<Inventory> inventories = _db.Inventories;
List<string> cmpNm = new List<string>; List<string> brnNm = new List<string>; List<string> prnNm = new List<string>;
foreach (var item in inventories) { string x1 = "";
Company cmps = _db. Company.SingleOrDefault(i => i.Id == item.CompanyId); if (cmps!= null)
{ x1 = cmps.Name; } cmpNm.Add(x1); x1 = "";
Branch brns = _db. Branch.SingleOrDefault(i => i.Id == item. Branch Id); if (brns!= null) { x1 = brns.Name; } brnNm.Add(x1); x1 = "";
Person pers = _db.Persons.SingleOrDefault(i => i.Id == item. PersonId);
if (pers!= null) { x1 = pers.Name; } prnNm.Add(x1);
// the MyViewModel now needs to populated with all its properties and generate an index
// something along the line of
new MyViewModel { PageNumber= pagenumber, PageSize= pagesize, TotalRows=Totalrows, Inventories = inventories; CmpNm=cmpNm, BrnNm=brnNm, PrnNm=prnNm}
View (How to create the Index is the problem)
#model.Project.ViewModels.MyViewModel
#{ var grid = new WebGrid(Model.Inventories, Model.TotalRows, rowsPerPage: Model.PageSize); }
#grid.GetHtml( columns: grid.Columns(
Grid.Column(“PrnNm”, header: "Person", format: #Model.PrnNm.ElementAt(Index))
Grid.Column(“BrnNm”, header: "Branch", format: #Model.BrnNm.ElementAt(Index))
Grid.Column(“CmpNm”, header: "Company", format: #Model.CmpNm.ElementAt(Index))
grid.Column("RtNum", header: "Route"),
grid.Column("AcntNum", header: "Account"),
grid.Column("CardNum", header: "Card")
… ) )
What the grid should look like is self-evident.
It's pretty unclear what is your goal. But no matter what it is I would recommend you to define a real view model reflecting the requirements of your view and containing only the information you are interested in seeing in this grid:
public class InventoryViewModel
{
public int Id { get; set; }
public string PersonName { get; set; }
public string BranchName { get; set; }
public string CompanyName { get; set; }
public string RouteNumber { get; set; }
public string AccountNumber { get; set; }
public string CardNumber { get; set; }
}
Now you could have the main view model:
public class MyViewModel
{
public int PageNumber { get; set; }
public int TotalRows { get; set; }
public IEnumerable<InventoryViewModel> Inventories { get; set; }
}
Alright, the view is now obvious:
#model MyViewModel
#{
var grid = new WebGrid(
Model.Inventories,
rowsPerPage: Model.PageSize
);
}
#grid.GetHtml(
columns: grid.Columns(
grid.Column("Id", header: "Inventory id"),
grid.Column("PersonName", header: "Person"),
grid.Column("BranchName", header: "Branch"),
grid.Column("CompanyName", header: "Company"),
grid.Column("RouteNumber", header: "Route"),
grid.Column("AccountNumber", header: "Account"),
grid.Column("CardNumber", header: "Card")
)
)
Now all that's left is build this view model in your controller. Since I don't know what you are trying to achieve here, whether you need an inner join or a left outer join on those columns, I will take as an example here a left outer join:
public ActionResult Index()
{
var inventories =
from inventory in _db.Inventories
join person in _db.Persons on inventory.PersonId equals person.Id into outerPerson
join company in _db.Companies on inventory.CompanyId equals company.Id into outerCompany
join branch in _db.Branch on inventory.BranchId equals branch.Id into outerBranch
from p in outerPerson.DefaultIfEmpty()
from c in outerCompany.DefaultIfEmpty()
from b in outerBranch.DefaultIfEmpty()
select new InventoryViewModel
{
PersonName = (p == null) ? string.Empty : p.Name,
CompanyName = (c == null) ? string.Empty : c.Name,
BranchName = (b == null) ? string.Empty : b.Name,
Id = inventory.Id,
AccountNumber = inventory.AcntNum,
CardNumber = inventory.CardNum,
RouteNumber = inventory.RtNum
};
var model = new MyViewModel
{
PageSize = 5,
// TODO: paging
Inventories = inventories.ToList()
};
return View(model);
}
And that's pretty much it. Of course in this example I am leaving the pagination of the Inventories collection for you. It should be pretty trivial now to .Skip() and .Take() the number of records you need.
As you can see ASP.NET MVC is extremely simple. You define a view model to reflect the exact requirements of what you need to show in the view and then populate this view model in the controller. Most people avoid view models because they fail to populate them, probably due to lack of knowledge of the underlying data access technology they are using. As you can see in this example the difficulty doesn't lie in ASP.NET MVC at all. It lies in the LINQ query. But LINQ has strictly nothing to do with MVC. It is something that should be learned apart from MVC. When you are doing MVC always think in terms of view models and what information you need to present to the user. Don't think in terms of what you have in your database or wherever this information should come from.

Resources