Linq join query - asp.net-mvc-2

For example
DB with 2 Tables
Book [BookId (int), Title (nvarchar), ShowInWebshop (bit)] and
InventoryDetail [InventoryDetailId (int), BookId (int), Quantity (int)]
Execute: SELECT * FROM Books LEFT JOIN InventoryDetails ON books.BookId = InventoryDetails.BookId
The output shows all Book columns and related InventoryDetails columns (including the InventoryDetails.BookId column)
..so far so good ...
Trying to transform this query into a Linq one (using LinqPad, comparing several examples, common sense, etc.) I compilated the following generic List (because I wanted a present a list)
private List<Book> Books(int count){
var books = webshopDB.Books
.Join<Book, InventoryDetail, int, Book>( webshopDB.InventoryDetails,
b => b.BookId,
i => i.BookId,
(b, i) => b )
.Where(b => b.ShowInWebshop == true)
.Take(count)
.ToList();
return books
}
This module returns a list of books! Not the one I expected, though! It returns only book details such as Title and ShowOnSite NOT the details from the InventoryDetail table: Quantity
What do I forget?

The result how it works so far ...
public ActionResult Index()
{
// This return a list of tuples {(WebshopDB.Models.Book, WebshopDB.Models.InventoryDetail)}
// Each tuple containing two items:
// > Item1 {WebshopDB.Models.Book}
// > Item2 {WebshopDB.Models.InventoryDetail}
var tuple_books = ListOfTuples_BookInventoryDetail(5);
...
// next step(s)
// add a ViewModel viewmodel
// ...
return (viewmodel);
}
private List<Tuple<Book, InventoryDetail>> ListOfTuples_BookInventoryDetail(int count)
{
var list_of_tuples = new List<Tuple<Book, InventoryDetail>>();
var showbooks = webshopDB.Books
.Join(webshopDB.InventoryDetails, b => b.BookId, i => i.BookId, (b, i) => new { b = b, i = i })
.Where(o => (o.b.ShowInWebshop == true))
.Where(o => o.b.BookThumbUrl.Contains(".jpg"))
.OrderByDescending(o => o.b.OrderDetails.Count())
.Take(count);
foreach (var item in showbooks)
{
list_of_tuples.Add( Tuple.Create<Book, InventoryDetail>( (item.b), (item.i) ) );
}
return list_of_tuples;
}

You need to select from both tables, e.g.
from b in webshop.Books
from i in webshopDB.InventoryDetails
where i.BookId = b.BookId
select b.BookId, b.Title, b.ShowInWebshop, i.InventoryDetailId, i.Quantity

You're getting books because you specified that in your Join statement with the final selector of => b. You want to select both, so use this:
var query = webshopDB.Books.Join(webshopDB.InventoryDetails,
b => b.BookId, i => i.BookId,
(b, i) => new { Book = b, InventoryDetail = i });
Then when you iterate over the results you can use:
foreach (var item in query)
{
Console.WriteLine(item.Book.SomeProperty);
Console.WriteLine(item.InventoryDetail.SomeProperty);
}
The other problem with your method is that the return type is a List<Book>. So the above won't work since a Book class is separate from an InventoryDetail class. You need to setup a new class to encompass both, or use a Tuple<Book, InventoryDetail> if using .NET 4.0.
To return a particular property you could modify the statement to:
var query = webshopDB.Books.Join(webshopDB.InventoryDetails,
b => b.BookId, i => i.BookId,
(b, i) => new { b.BookId, i.Quantity });
Again, you need an appropriate return type if you're returning a List<T>.
EDIT: to get a Dictionary<Book, InventoryDetail> you could use the earlier query as follows:
var query = webshopDB.Books.Join(webshopDB.InventoryDetails,
b => b.BookId, i => i.BookId,
(b, i) => new { Book = b, InventoryDetail = i })
.ToDictionary(o => o.Book, o => o.InventoryDetail);
Of course you can use the Where and Take as necessary before the ToDictionary call. You can also project just the properties you need as the query just before this one. You need to project it into an anonymous type via the new keyword (it was missing earlier so take another look at it).

Quering the answer of SoftwareRockstar in LinqPad4 generates errors! So, that's not the way!
//This does works (in LinqPad4)
from b in webshopDB.Books
from i in webshopDB.InventoryDetails
where b.BookId == i.BookId
where b.ShowInWebshop == true
select new { b.Title, i.Quantity, b.ShowInWebshop }

Related

Group By with Entity Framework

enter image description hereI have a code. And there you need to make a grouping by name.
//<date,<partid,amount>>
Dictionary<string, Dictionary<int, double>> emSpending = new Dictionary<string, Dictionary<int, double>>();
foreach (Orders order in db.Orders.ToList())
{
foreach (OrderItems orderitem in order.OrderItems.ToList())
{
if (!emSpending.ContainsKey(order.Date.ToString("yyyy-MM"))) emSpending.Add(order.Date.ToString("yyyy-MM"), new Dictionary<int, double>());
if (!emSpending[order.Date.ToString("yyyy-MM")].ContainsKey(Convert.ToInt32(orderitem.PartID))) emSpending[order.Date.ToString("yyyy-MM")].Add(Convert.ToInt32(orderitem.PartID), 0);
emSpending[order.Date.ToString("yyyy-MM")][Convert.ToInt32(orderitem.PartID)] += Convert.ToDouble(orderitem.Amount);
}
}
DataGridViewColumn col1 = new DataGridViewColumn();
col1.CellTemplate = new DataGridViewTextBoxCell();
col1.Name = "Department";
col1.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
col1.HeaderText = "Department";
dgvEMSpending.Columns.Add(col1);
foreach (string date in emSpending.Keys)
{
DataGridViewColumn col = new DataGridViewColumn();
col.Name = date;
col.HeaderText = date;
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
col.CellTemplate = new DataGridViewTextBoxCell();
dgvEMSpending.Columns.Add(col);
}
List<string> allKey = emSpending.Keys.ToList();
foreach (string date in allKey)
if (date == "Department") continue;
else
{
dgvEMSpending.Rows.Add();
foreach (int partid in emSpending[date].Keys)
{
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts.Where(x => x.ID == partid).SingleOrDefault().Name.GroupBy(Name);
for (int i = 1; i < dgvEMSpending.Columns.Count; i++)
{
if (!emSpending.ContainsKey(dgvEMSpending.Columns[i].Name)) emSpending.Add(dgvEMSpending.Columns[i].Name, new Dictionary<int, double>());
if (!emSpending[dgvEMSpending.Columns[i].Name].ContainsKey(partid)) emSpending[dgvEMSpending.Columns[i].Name].Add(partid, 0);
double val = emSpending[dgvEMSpending.Columns[i].Name][partid];
dgvEMSpending.Rows[dgvEMSpending.RowCount - 1].Cells[i].Value = val;
}
}
}
I tried to use group by myself, but something doesn't work. It just outputs the same names, and I want to group them so that there is a grouping. Pls helped to me.
Ok, a few issues to help you out first. This code:
foreach (Orders order in db.Orders.ToList())
{
foreach (OrderItems orderitem in order.OrderItems.ToList())
{
if (!emSpending.ContainsKey(order.Date.ToString("yyyy-MM"))) emSpending.Add(order.Date.ToString("yyyy-MM"), new Dictionary<int, double>());
if (!emSpending[order.Date.ToString("yyyy-MM")].ContainsKey(Convert.ToInt32(orderitem.PartID))) emSpending[order.Date.ToString("yyyy-MM")].Add(Convert.ToInt32(orderitem.PartID), 0);
emSpending[order.Date.ToString("yyyy-MM")][Convert.ToInt32(orderitem.PartID)] += Convert.ToDouble(orderitem.Amount);
}
}
Right off the bat this is going to trip lazy loading on OrderItems. If you have 10 orders 1-10 you're going to be running 11 queries against the database:
SELECT * FROM Orders;
SELECT * FROM OrderItems WHERE OrderId = 1;
SELECT * FROM OrderItems WHERE OrderId = 2;
// ...
SELECT * FROM OrderItems WHERE OrderId = 10;
Now if you have 100 orders or 1000 orders, you should see the problem. At a minimum ensure that if you are touching a collection or reference on entities you are loading, eager load it with Include:
foreach (Orders order in db.Orders.Include(x => x.OrderItems).ToList())
This will run a single query that fetches the Orders and their OrderItems. However, if you have a LOT of rows this is going to take a while and consume a LOT of memory.
The next tip is "only load what you need". You need 1 field from Order and 2 fields from OrderItem. So why load everything from both tables??
var orderItemDetails = db.Orders
.SelectMany(o => o.OrderItems.Select(oi => new { o.Date, oi.PartId, oi.Amount })
.ToList();
This would give us just the Order date, and each Part ID and Amount. Now that this data is in memory we can group it to populate your desired dictionary structure without having to iterate over it row by row.
var emSpending = orderItemDetails.GroupBy(x => x.Date.ToString("yyyy-MM"))
.ToDictionary(g => g.Key,
g => g.GroupBy(y => y.PartId)
.ToDictionary(g2 => g2.Key, g2 => g2.Sum(z => z.Amount)));
Depending on the Types in your entities you may need to insert casts. This first groups the outer dictionary of the yyyy-MM of the order dates, then it groups the remaining data for each date by part ID, and sums the Amount.
Now relating to your question, from your code example I'm guessing the problem area you are facing is this line:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts
.Where(x => x.ID == partid)
.SingleOrDefault().Name.GroupBy(Name);
Now the question would be to explain what exactly you are expecting from this? You are fetching a single Part by ID. How would you expect this to be "grouped"?
If you want to display the Part name instead of the PartId then I believe you would just want to Select the Part Name:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts
.Where(x => x.ID == partid)
.Select(x => x.Name)
.SingleOrDefault();
We can go one better to fetch the Part names for each used product in one hit using our loaded order details:
var partIds = orderItemDetails
.Select(x=> x.PartId)
.Distinct()
.ToList();
var partDetails = db.Parts
.Where(x => partIds.Contains(x.ID))
.ToDictionary(x => x.ID, x => x.Name);
This fetches us a dictionary set indexed by ID for the part names, it would be done outside of the loop after we had loaded the orderItemDetails. Now we don't have to go to the DB with every row:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = partDetails[partId];

How to apply Where condition on Children Table in Entity Framework

I have following SQL Query , due to some limitation because there are many more conditions in this query I have to convert this query to LINQ.
SELECT
sh.BarCode
FROM
Bars AS sh
INNER JOIN BarDetail AS detail ON
detail.BarCode = sh.BarCode
AND
detail.IsActive = 1
INNER JOIN BarStatus AS st ON
st.BarCode = sh.BarCode
AND
st.IsActive = 1
So I have done this so far at LINQ
var queryAble = _context.BarDetail
.Include(x => x.Bar)
.Include(x => x.Bar)
.ThenInclude(y => y.BarStatus)
.Where(x => x.IsActive == true)
.AsQueryable();
I want to apply condition on barstatus as well; condition is barstatus with IsActive == true. I am unable to do it .
I don't want to do it as raw SQL with DbCommand, I'd like to do it entirely using only Linq-to-Entities.
How would I do this maybe this way but its not working
var queryAble = _context.BarDetail
.Include(x => x.Bar)
.Include(x => x.Bar)
.ThenInclude(y => y.BarStatus)
.Where(x => x.IsActive == true && x.Bar.BarStatus[SOMETHING HERE])
.AsQueryable();
This is direct translation of your SQL to LINQ. Note that Include intorduced not for building query but for loading related data.
var query =
from bar in _context.Bar
from detail in bar.Details
where detail.IsActive && bar.IsActive && bar.BarStatus.IsActive
select bar.BarCode;
As I remarked in my comment, while your original SQL query works, it's best for JOIN clauses to use only key (or tuple) equality, while other predicates should be in the WHERE clause. Following that pattern shouldn't cause any changes to your runtime query execution plan, but I feel it's keeping with the relational-calculus that SQL is based on - and it also means you can instantly check if a JOIN is correct or not because you'll always be using only primary-key and foreign-key columns (which are presumably already indexed... right?).
So your query becomes:
SELECT
b.BarCode
FROM
Bars AS b
INNER JOIN BarDetail AS d ON d.BarCode = b.BarCode
INNER JOIN BarStatus AS s ON s.BarCode = b.BarCode
WHERE
d.IsActive = 1
AND
s.IsActive = 1
...which is easier to translate into Linq-to-Entities:
Also:
You don't need the .AsQueryable() call: all non-materialized queries created from DbContext's DbSet<T> will be IQueryable<T> already.
As you have navigation-properties you don't need to do a manual Join.
IQueryable<String> q = _context.BarCode
// .Include( b => b.BarDetail )
// .Include( b => b.BarStatus )
.Where( b =>
b.BarDetail.IsActive == true
&&
b.BarStatus.IsActive == true
)
.Select( b => b.BarCode );
List<String> list = await q.ToListAsync( cancellationToken ).ConfigureAwait(false);
Update: Without b.BarDetail.IsActive == true
I can not directly add Include( b => b.BarStatus ) because BarStatus doesn't have direct relationship with BarCode, its linked with BarDetail. So first we go into BarDetail and then after that we go in BarStatus using BarDetail
You can still do a manual JOIN:
IQueryable<String> q = _context.BarCode
// .Include( b => b.BarDetail )
// .Include( b => b.BarStatus )
.Join( _context.BarStatus, s => s.BarCode, b => b.BarCode, ( s, b )
=> new { BarStatus = s, BarCode = b, BarDetail = b.BarDetail } )
.Where( t =>
t.BarDetail.IsActive == true
&&
t.BarStatus.IsActive == true
)
.Select( b => b.BarCode );
List<String> list = await q.ToListAsync( cancellationToken ).ConfigureAwait(false);

EF Core: group by => InvalidOperationException

I'm working on kind of education project (for myself) to learn EF Core -> code first approach and I'm struggling what is wrong with my query. A have two simple entitities: article, and user and I want to group them to get an list of objects that contain list of all user with related articles.
{
var authorRowModels =
from articles in _myContext.Articles
join authors in _myContext.Users on articles.Author.Id equals authors.Id
group new { articles, authors } by authors into authorWithArticles
select new
{
AuthorFirstName = authorWithArticles.Key.FirstName,
AuthorLastName = authorWithArticles.Key.LastName,
Articles = authorWithArticles.Select(x => x.articles)
};
return View(authorRowModels.AsEnumerable().Select(x => new AuthorRowModel
{
AuthorFirstName = x.AuthorFirstName,
AuthorLastName = x.AuthorLastName,
Articles = x.Articles.ToList()
}));
}
but I'm getting an error:
InvalidOperationException: The LINQ expression 'DbSet .LeftJoin( outer: DbSet, inner: a => EF.Property<Nullable>(a, "AuthorId"), outerKeySelector: u0 => EF.Property<Nullable>(u0, "Id"), innerKeySelector: (o, i) => new TransparentIdentifier<Article, User>( Outer = o, Inner = i )) .Join( outer: DbSet, inner: a => a.Inner.Id, outerKeySelector: u => u.Id, innerKeySelector: (a, u) => new TransparentIdentifier<TransparentIdentifier<Article, User>, User>( Outer = a, Inner = u )) .GroupBy( source: ti => ti.Inner, keySelector: ti => new { articles = ti.Outer.Outer, authors = ti.Inner })' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
and I have no idea what EF not able to translate. Also if you have a hint how to rewrite this query in better way maybe even without grouping it'd be great :)
Thank you.
Using join in EF LINQ is almost always the wrong solution.
With a Navigation Property this can simply be something like:
var authorRowModels = from a in _myContext.Users
where a.Articles.Any()
select new
{
a.FirstName,
a.LastName,
a.Articles
};

Can we use a DbContext within a Linq Select Expression?

Is it a good practice or a convention to use dbContext within a Select linq query as following . If not what is the right convention or alternative to do so ?
dbContext.Employees.Select(x=>{
**Name = dbContext.ContactInformation.Where(y=>y.Id = x.Id),**
Id = x.Id
})
Why do not you have a navigationPropery from Employee to ContactInformation? look here
var result = dbContext.Employees.Include(e => e.ContactInformation);
You can also use a Join.
var res = dbContext.Employees.Join(ContactInformation,
e => e.Id,
c => c.Id,
(e, c) => new { e, c })
.Select(ec => ec.e);

how to implement EF inner join with given filter?

SELECT DISTINCT k.* FROM [dbo].[kinds] K
INNER JOIN KindGraphic KG ON K.KindId = KG.KindId
INNER JOIN Graphics G ON KG.GraphicId = G.GraphicId
WHERE K.CategoryType = 2
AND G.IsSpecial = 1
How to write this in EF ? I am new to EF. I m using dbContex for my MVC project.
Make Note that "KindGraphic" table is mapped liked this ways
so I can not use this method https://stackoverflow.com/a/21986882/3264939
modelBuilder.Entity<Kind>()
.HasMany(c => c.Graphics)
.WithMany(g => g.Kinds)
.Map(t => t.MapLeftKey("KindId")
.MapRightKey("GraphicId")
.ToTable("KindGraphic"));
The result from your original query is some kind of complex result. So without selecting the exact columns (instead of using *), I assume the result is contained in an anonymous type like this:
{
Kind,
Graphic
}
I understand that KindGraphic is some kind of junction (join) table, so it's info is not important to include in the result (we can access KindId from Kind and GraphicId from Graphic). Here is the LINQ query:
var result = context.kinds.Where(e => e.CategoryType == 2)
.SelectMany(e=> e.Graphics.Where(g=>g.IsSpecial == 1),
(e, g) => new { Kind = e, Graphic = g} );
After your edit to use distinct, the query can be translated as you want all kinds having category type = 2 and any Graphics with IsSpecial = 1. So it should be like this:
var result = context.kinds.Where(e => e.CategoryType == 2 &&
e.Graphics.Any(g=>g.IsSpecial == 1));