I'm developing an API with .NET Core 3.0 and Entity Framework 6.4.0, as a database I'm using Mysql.
I'm having trouble making a query that returns strings that contain special characters.
Database:
API returns:
Below follows the method that performs the query. I just pass a where clause and get a list of objects:
public virtual async Task<List<TEntity>> Get(Expression<Func<TEntity, bool>> where,
bool asNoTracking = true,
int take = TAKE)
{
if (asNoTracking)
return await DbSet.AsNoTracking()
.Where(where)
.Take(take)
.ToListAsync()
.ConfigureAwait(false);
return await DbSet.Where(where)
.Take(take)
.ToListAsync()
.ConfigureAwait(false);
}
In debugging I identified that as soon as the value is read from the database it is already incorrect.
Where's the error?
Related
I'm trying to retrieve a list of missing Ids in database from another list of Ids in Entity Framework Core.
Is there a way to get this call in one line?
public static async Task<IEnumerable<TKey>> GetMissingIds<T, TKey>(
this IQueryable<T> db, IEnumerable<TKey> ids)
where T : BaseEntity<TKey>
{
var existingIds = await db
.AsNoTracking()
.Where(entity => ids.Contains(entity.Id))
.Select(entity => entity.Id)
.ToListAsync();
return ids.Except(existingIds);
}
EF Core supports only Contains with local collections (with small exceptions), so there is no effective way to retrieve Ids which are not present in database via LINQ Query.
Anyway there is third-party extensions which can do that linq2db.EntityFrameworkCore (note that I'm one of the creators).
Using this extension you can join local collection to LINQ query:
public static Task<IEnumerable<TKey>> GetMissingIds<T, TKey>(
this IQueryable<T> query, IEnumerable<TKey> ids, CabcellationToken cancellationToken = default)
where T : BaseEntity<TKey>
{
// we need context to retrieve options and mapping information from EF Core
var context = LinqToDBForEFTools.GetCurrentContext(query) ?? throw new InvalidOperationException();
// create linq2db connection
using var db = context.CreateLinqToDbConnection();
var resultQuery =
from id in ids.AsQueryable(db) // transform Ids to queryable
join e in query on id equals e.Id into gj
from e in gj.DefaultIfEmpty()
where e == null
select id;
// there can be collision with EF Core async extensions, so use ToListAsyncLinqToDB
return resultQuery.ToListAsyncLinqToDB(cancellationToken);
}
This is the sample of generated query:
SELECT
[id].[item]
FROM
(VALUES
(10248), (10249), (10250), (10251), (10252), (10253), (10254),
(10255), (10256), (10257), (10023)
) [id]([item])
LEFT JOIN (
SELECT
[e].[OrderID] as [e]
FROM
[Orders] [e]
) [t1] ON [id].[item] = [t1].[e]
WHERE
[t1].[e] IS NULL
The DbContext DbSet<T>.Load / DbSet<T>.LoadAsync methods return void and Task respectively: they execute queries and then add the loaded/instantiated entity objects into the DbContext's DbSet and update the navigation properties and reverse-navigation of already-loaded objects, but they don't return any information about what they loaded: there doesn't seem to be a way of getting the actual count of the number of rows that were loaded.
Which is surprising, considering that the SaveChanges / SaveChangesAsync method does return the number of rows affected by any DML statements it executes.
I know there's a workaround in that I could use ToList/ToListAsync instead and then use the List<T>.Count property, but that's defeating the point of using Load/LoadAsync.
For example, consider this two-step query operation:
async Task<PageViewModel> LoadOrdersAsync( Int32 customerId, Expression<Func<Order,Boolean>> predicate )
{
// Step 1:
List<Order> orders = await this.dbContext.Orders
.Where( o => o.CustomerId == customerId )
.Where( predicate )
.ToListAsync()
.ConfigureAwait(false);
// Step 1.5:
List<Int32> orderIds = orders.Select( o => o.OrderId ).ToList();
// Step 2:
await this.dbContext.OrderItems
.Where( i => orderIds.Contains( i.OrderId ) )
.LoadAsync()
.ConfigureAwait(false);
// Done!
return new PageViewModel( orders );
}
I want to get the quantity of OrderItem entities that were loaded in the second step, but as far as I know that isn't possible without using ToList/ToListAsync.
You’re right, there is no easy way to get the number of loaded entries of the Load. It is essentially the same as ToList without creating the list and adding the loaded elements to it. If you really don’t want to use ToList, one option is to access the DbContext.ChangeTracker and get the number of entries from that:
var entriesBefore = context.ChangeTracker.Entries().Count();
// load more entities
var loaded = context.ChangeTracker.Entries().Count() - entriesBefore;
Note, that this is not accurate when you include other, related entities in your query.
I want to update the itemsToUpdate collection.
This collection is already used in a query thus the resulting entities are already tracked in the context local property.
What is the most efficient way of overriding properties of the context.items.Local property from the itemsToUpdate collection?
private async Task<IEnumerable<item>> GetitemsAsync(IEnumerable<item> itemIds)
{
return await context.items.Where(t => itemIds.Select(x => x.Id).Contains(t.Id)).ToListAsync();
}
public async Task Update(...)
{
// Update
var queryUpdateitems = await GetitemsAsync(itemsToUpdate);
bool canUpdate = queryUpdateitems.All(t => t.UserId == userId);
if (!canUpdate)
{
throw new NotAuthorizedException();
}
else
{
// update here the itemsToUpdate collection
}
context.SaveChangesAsync();
}
In your case, you know that you have to update all these items, you just want to make sure that current user can update all items (by comparing Item.UserId). Instead of fetching all the existing items from database to make the check, you can query database to give result of the check and then you can just send update to database if check is true.
var itemIds = itemsToUpdate.Select(x => x.Id).ToList();
var canUpdate = await db.Blogs.Where(b => itemIds.Contains(b.Id)).AllAsync(t => t.UserId == userId);
if (canUpdate)
{
db.UpdateRange(itemsToUpdate);
}
else
{
throw new NotSupportedException();
}
await db.SaveChangesAsync();
Here, you have to make list of itemIds first because EF cannot inline list of items in a query and will do evaluation on client otherwise. That means EF is fetching whole table. Same is true for your GetitemsAsync method. It also queries whole table. Consider creating itemIds locally in that method too.
Once you pass in List<int> in the method EF will be happy to inline it in query and for query of canUpdate it will sent single query to database and fetch just true/false from database. Then you can use UpdateRange directly since there are nomore tracking records. Since it does not fetch all items from database, it will be faster too.
I am using Entity Framework 7 Code First
I have a function that needs to returns a list of Countries(ids,Names) linked to a User.
The User isn't directly linked to the Country but is linked via the City. City is linked to State. State is linked to Country.
I decided to use a GroupBy to get the list of countries.
public async Task<IEnumerable<Country>> Search(int userId)
{
var table = await _db.Cities
.Include(ci => ci.States.Country)
.Select(ci => ci.States.Country)
.OrderBy(co => co.CountryName)
.GroupBy(co=>co.pk_CountryId)
.ToListAsync()
;
return table;
}
However I get the error:
CS0266 Cannot implicitly convert type
'System.Collections.Generic.List <System.Linq.IGrouping> to
'System.Collections.Generic.List'
How do I return a variable IEnumerable<Country> as that is what the receiving code expects i.e. a list of Countries?
Am I doing my grouping correct?
For performance I assume grouping is better than a distinct or a contains
If you want to have the distinct countries, you can use a select afterwards to select the first country in each IGrouping<int,Country>:
public async Task<IEnumerable<Country>> Search(int userId)
{
return await _db.Cities
.Include(ci => ci.States.Country)
.Select(ci => ci.States.Country)
.OrderBy(co => co.CountryName)
.GroupBy(co=>co.pk_CountryId)
.Select(co => co.FirstOrDefault())
.ToListAsync();
}
Also a little sidenote, the Include isn't necessary here, eager loading the countries would only be useful if you were to return the States and wanted its Country property be populated. The Select makes sure you're grabbing the Country, you're not even fetching the states anymore from database.
I want to fetch the candidate and the work exp where it is not deleted. I am using repository pattern in my c# app mvc.
Kind of having trouble filtering the record and its related child entities
I have list of candidates which have collection of workexp kind of throws error saying cannot build expression from the body.
I tried putting out anonymous object but error still persist, but if I use a VM or DTO for returning the data the query works.
It's like EF doesn't like newing up of the existing entity within its current context.
var candidate = dbcontext.candidate
.where(c=>c.candiate.ID == id).include(c=>c.WorkExperience)
.select(e=>new candidate
{
WorkExperience = e.WorkExperience.where(k=>k.isdeleted==false).tolist()
});
Is there any workaround for this?
You cannot call ToList in the expression that is traslated to SQL. Alternatively, you can start you query from selecting from WorkExperience table. I'm not aware of the structure of your database, but something like this might work:
var candidate = dbcontext.WorkExperience
.Include(exp => exp.Candidate)
.Where(exp => exp.isdeleted == false && exp.Candidate.ID == id)
.GroupBy(exp => exp.Candidate)
.ToArray() //query actually gets executed and return grouped data from the DB
.Select(groped => new {
Candidate = grouped.Key,
Experience = grouped.ToArray()
});
var candidate =
from(dbcontext.candidate.Include(c=>c.WorkExperience)
where(c=>c.candiate.ID == id)
select c).ToList().Select(cand => new candidate{WorkExperience = cand.WorkExperience.where(k=>k.isdeleted==false).tolist()});