Can we use a DbContext within a Linq Select Expression? - entity-framework

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);

Related

Using string_agg in the many-to-many relation

I have entities like Product(Id, Name) and Keyword(Id, Description), and there is a many-to-many relationship between them.
The essence of my task is the following, I need to do a full-text search on Name and Description columns, using EF CORE 6
I already have some SQL code that works fine.
SELECT a."Id", a."Name" as name, k.txt
FROM "Products" AS a
LEFT JOIN (
SELECT x."ProductsId" as Id, string_agg(y."Description", ' ') as txt
FROM "ProductKeywords" x
JOIN "Keywords" y ON y."Id" = x."KeywordId"
GROUP BY 1
) k ON a."Id" = k.Id
WHERE to_tsvector(concat_ws(' ', a."Name", k.txt))
## to_tsquery('Some text');
And I need to write some LINQ code that will do something similar, but I have a problem with string_agg, and I don't understand how to implement it in LINQ and EF CORE will reflect it correctly
I tried to do the following
var products = _context.Products
.Select(e => new
{
Id = e.Id,
Name = e.Name,
Keywords = string.Join(" ", e.Keywords.Select(q => q.Description))
}).Where(e => EF.Functions.ToTsVector(e.Keywords).Matches("Some text")).ToList();
But I get an error, and it's most likely because of string.Join
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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
Got the result, using linq2db
var query = _context.Products.ToLinqToDB()
.LeftJoin(_context.ProductsKeywords.ToLinqToDB().GroupBy(r => r.ProductId).Select(e => new {
Key = e.Key,
Txt = e.StringAggregate(",", t => t.Keyword.Description).ToValue()
}),
(product, productKeyword) => product.Id == productKeyword.Key,
(i, j) => new {
Id = i.Id,
Txt = j.Txt
}).Select(e => new {
Id = e.Id,
Txt = EF.Functions.ToTsVector(e.Txt)
}).Where(w => w.Txt.Matches("Some text"));

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
};

ef core select records from tableA with matching fields in tableB

I'm trying to translate a query from raw sql to linq statement in EF Core 2
Query is something like this
SELECT * FROM TableA
WHERE FieldA = ConditionA
AND FieldB IN (SELECT FieldC FROM TableB WHERE FieldD = ConditionB)
FieldA and FieldB are from TableA, FieldC and FieldD are from TableB
I need all fields from TableA and none from TableB returned, so it should be something like
return Context.TableA
.Where(x => x.FieldA == ConditionA)
.[ some code here ]
.ToList()
my current solution is to get two distinct result sets and join them in code, something like this
var listA = Context.TableA
.Where(x => x.FieldA == ConditionA).ToList();
var listB = Context.TableB
.Where(x => x.FieldD == ConditionB).Select(x => x.FieldC).ToList();
listA.RemoveAll(x => !listB.Contains(x.FieldB);
return listA;
i hope it works, i still have to run tests on it, but i'm looking for a better solution (if anything exists)
You can simply use Contains function in query like this :
var ids = Context.TableB
.Where(p => p.FieldD == conditionD)
.Select(p => p.FieldC);
var result = Context.TableA
.Where(p => ids.Contains(p.FieldB))
.ToList();
It's a simple join that needs to be applied -
var result = (from a in Context.TableA.Where(x => x.FieldA == ConditionA )
join b in Context.TableB.Where(x => x.FieldD == ConditionB) on a.FieldB equals b.FieldC
select new {a}
).ToList();

Linq join query

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 }