Entity Framework Core - Use generated query - entity-framework-core

I have a structure of 26 tables which I want to load data from the database and get them inside the code. For example I use the Include method to join the related data of an entity:
var Application = dbContext.Applications
.Include(a => a.PwEmployee)
.Include(a => a.Promoters).ThenInclude(f => f.Addresses)
.Include(a => a.Promoters).ThenInclude(f => f.People)
... and many more
I don't want to include the generated query since it is not that readable and also not the problem here. What I want to do is use the generated query in a stored procedure and do this:
List<Application> Applications = await dbContext.Applications.FromSqlRaw("Exec GetFullApplication {0}", uuid).ToListAsync();
When I do this with the generated SQL query from EF Core, I get an exception:
System.ArgumentException: An item with the same key has already been added. Key: ID
When it works from EF Core to the database, shouldn't it work also the other way around with said SQL statement that EF Core generated? Is there any performance improvement to expect when doing this?

Related

Expression is invalid in an Include operation [duplicate]

I'm transfering my .NET Framework (EF6) code to ASP.NET Core (EF Core), and I stumbled upon this issue. Here is some example code:
In EF6 I use Include() and Select() for eager-loading:
return _context.Post
.Include(p => p.PostAuthor.Select(pa => pa.Author).Select(a => a.Interests))
PostAuthor is a junction table and there is also a Junction table "AuthorInterest" which I didn't need to involve in EF6 (Select goes straight to a.Interests).
Anyway, I can see that in EF7 this is reworked, meaning that I should use ThenInclude() for nested queries now. However...
return _context.Post
.Include(p => p.PostAuthor)
.ThenInclude(pa => pa.Select(pa2 => pa2.Author))
...etc
The above code fails because of the Select() statement. The documentation on https://docs.efproject.net/en/latest/querying/related-data.html seems to suggest that I don't need it and I can access Author immediately, but I get an ICollection in the last lambda displayed, so I obviously need the Select(). I go through multiple junction tables further on in the query, but for simplicity's sake, let's just focus on the first one.
How do I make this work?
but I get an ICollection in the last lambda displayed, so I obviously need the Select()
No, you don't. EF Core Include / ThenInclude totally replace the need of Select / SelectMany used in EF6. Both they have separate overloads for collection and reference type navigation properties. If you use the overload with collection, ThenInclude operates on the type of the collection element, so at the end you always end up with a single entity type.
In your case, pa should resolve to your junction table element type, so Author should be directly accessible.
For instance the EF6 include chain:
.Include(p => p.PostAuthor.Select(pa => pa.Author).Select(a => a.Interests))
translates to EF Core:
.Include(p => p.PostAuthor).ThenInclude(pa => pa.Author).ThenInclude(a => a.Interests)

Entity Framework navigation collections performance issue [duplicate]

This question already has answers here:
EF: Create/Remove relation from Many-to-Many relations when `AutoDetectChangesEnabled` and `ProxyCreationEnabled` are disabled on DbContext
(3 answers)
Closed 3 years ago.
I have a many-to-many relation in Entity Framework, in the mapping, EF allow us to specify the keys and table name. In my example below, a user can have many device logs and a device log can belong to many users.
HasMany(e => e.DeviceLogs)
.WithMany(e => e.Users)
.Map(m => m.ToTable("UserDeviceLog")
.MapLeftKey("UserId")
.MapRightKey("LogId")
);
Now, when i try to remove a device log from a user:
using (var context = new MyDbContext())
{
var user = context.Users.Find(userId);
var deviceLog = context.DeviceLogs.Find(logId);
user.DeviceLogs.Remove(deviceLog);
context.SaveChanges();
}
I know that I'm going to delete a relation from table "UserDeviceLog", not the device log itself.
This above code snippet works, but the problem is Entity Framework will load all device logs and then remove one record from table UserDeviceLog.
My application is blocked while execute the above logic. From the screenshot you can see, there is about 23 seconds between the two SQL statements. For the specified user, it has about 20K records(the first SQL statement). I'm guessing it was taken by transfering these 20K records from SQL Server to my application which cause my application very slow.
While this is unnecessary, how can i avoid it? I don't want to write a plain SQL delete statement, I prefer a solution in EF way.
Thank you!
This will occur because by accessing user.DeviceLogs you will lazy-load that collection.
If UserDeviceLogs is not accessible in the DbContext as a DbSet (which it typically would not as a many-to-many joining table/entity)...
What should get you there would be:
using (var context = new MyDbContext())
{
var userDeviceLog = context.Users.Where(x => x.UserId == userId)
.SelectMany(x => x.DeviceLogs.Where(l => l.LogId ==logId))
.SingleOrDefault();
if(userDeviceLog == null)
return;
context.Entry(userDeviceLog).State = EntityState.Deleted;
context.SaveChanges();
}
If a DeviceLog refers to any reference that also must be deleted you will need to handle that deletion as well. But this should unlink a user from a device log without loading all links.

Using EF Core ThenInclude() on Junction tables

I'm transfering my .NET Framework (EF6) code to ASP.NET Core (EF Core), and I stumbled upon this issue. Here is some example code:
In EF6 I use Include() and Select() for eager-loading:
return _context.Post
.Include(p => p.PostAuthor.Select(pa => pa.Author).Select(a => a.Interests))
PostAuthor is a junction table and there is also a Junction table "AuthorInterest" which I didn't need to involve in EF6 (Select goes straight to a.Interests).
Anyway, I can see that in EF7 this is reworked, meaning that I should use ThenInclude() for nested queries now. However...
return _context.Post
.Include(p => p.PostAuthor)
.ThenInclude(pa => pa.Select(pa2 => pa2.Author))
...etc
The above code fails because of the Select() statement. The documentation on https://docs.efproject.net/en/latest/querying/related-data.html seems to suggest that I don't need it and I can access Author immediately, but I get an ICollection in the last lambda displayed, so I obviously need the Select(). I go through multiple junction tables further on in the query, but for simplicity's sake, let's just focus on the first one.
How do I make this work?
but I get an ICollection in the last lambda displayed, so I obviously need the Select()
No, you don't. EF Core Include / ThenInclude totally replace the need of Select / SelectMany used in EF6. Both they have separate overloads for collection and reference type navigation properties. If you use the overload with collection, ThenInclude operates on the type of the collection element, so at the end you always end up with a single entity type.
In your case, pa should resolve to your junction table element type, so Author should be directly accessible.
For instance the EF6 include chain:
.Include(p => p.PostAuthor.Select(pa => pa.Author).Select(a => a.Interests))
translates to EF Core:
.Include(p => p.PostAuthor).ThenInclude(pa => pa.Author).ThenInclude(a => a.Interests)

EF Core Group On Nav Properties

Quick question regarding this issue.
I have an error unable to get the data from database,
Is there something issue with EF Core or my linq query?
var query = _postRepository.Table
.Where(x => x.Approval == true)
.Include(p => p.Industry)
.GroupBy(grp => new { grp.Industry.Id, grp.Industry.Name })
.Select(s => new JobPostStatistics
{
IndustryId = s.Key.Id,
IndustryName = s.Key.Name,
TotalJobPost = s.Count()
});
return query.Take(6).ToList();
InvalidOperationException: Sequence contains more than one element
Seems in EF RC1 GroupBy cause error, here is reported bug:
GroupBy query cause error Sequence contains more than one element
EF Core 2.1 will not only work, but it will also be capable of translating GroupBy to SQL GROUP BY. The expected release date is Q4 2017. Previous versions also support GroupBy but execute it on the client side, which can be a huge problem if you're using it to retrieve aggregates, like SUM, from the database. Imagine retrieving 2 million rows from the server to calculate the sum on the client side, instead of getting it directly from the database server.
In the meantime, you can always write your own SQL query and map it to an entity or anonymous type, using DbSet.FromSql, i.e. invoking FromSql as a member of an entity collection of your context.
Here you have enabled eager loading by using include keyword. You try with disable lazy loading
Server-side GroupBy-support of EF Core has been postponed to after the release: https://github.com/aspnet/EntityFramework/issues/2341
When you log the queries EF creates you can see that it does not create GROUP BY in the SQL.
Here somebody else posted the same error with another GroupBy query.
https://github.com/aspnet/EntityFramework/issues/4775
I had another error with GroupBy and Include(). As EF does not support GroupBy in the SQL and the grouping is done on the client side, I split my query into a server side part and a client side part. The first part queries from the database with the Include() into an anonymous type. Then I do the grouping on the client side.
var event2Country = DbContext.Event2Countries
.Include(j => j.Location)
.Select(j => new { j.Location.Id, j.Location.Name })
.AsNoTracking();
viewModel.MostUsedCountries = event2Country
.GroupBy(j => new { j.Id, j.Name })
.OrderByDescending(j => j.Count())
.ThenBy(j => j.Key.Id)
.Select(j => new SimpleListCountViewModel()
{
Id = j.Key.Id,
Name = j.Key.Name,
Count = j.Count()
})
.Take(15)
.ToList();
Currently you cannot use GroupBy in EF Core if you have a lot of data. A workaround would be to use FromSql. See issue 4959 on Github.

Restricting lazy loading while automapping EF entity type

Folks,
I am mapping EF objects to simple Pocos using automapper in my service layer. I have certain entities that have many relationships in EF,but I want to restrict how much of this gets pulled back from the db. One of these entities would map to a database table but would have many relationships to other tables that would appear as entity collections in the generated model (EDMX).
So I have created a "shallow" poco for the entity which only has poco properties for the first level of properties in the entity, i.e. some integer Ids instead of associated collections/entity collections. I map in the following manner....
var simplepocoforentity= Mapper.Map(_readOnlyDb.Single<EfEntity>(x => x.Id== Id),
new SimplPocoForEntity());
My question is.. because I am only mapping the entity to a simple poco here, can I be confident that EF will not try and query other non referenced data from the underlying db tables relationships when I do the mapping?
I know that I can investigate this via SQL Profiling, but I would appreciate any input befor going down that route.
Thanks
K.
It depends on the internal implementation of AutoMapper. I would assume that AutoMapper does not try to access the navigation properties of the entity (in which case lazy loading and an additional database query would kick in). I don't see a reason why AutoMapper should do this if you only map scalar properties. But who knows...
If you want to be on the safe side you could disable lazy loading temporarily:
try
{
_readOnlyDb.ContextOptions.LazyLoadingEnabled = false;
var simplepocoforentity = Mapper.Map(_readOnlyDb.Entities
.Single(x => x.Id == Id), new SimplPocoForEntity());
// ...
}
finally
{
_readOnlyDb.ContextOptions.LazyLoadingEnabled = true;
}
As a side note: Be aware that you load the full entity into the context with this approach. After that the mapping happens. Potentially you load more columns from the database than you really need (perhaps not in your special case here). In general I would do the mapping with a projection which ensures that the database only queries the columns which are needed:
var simplepocoforentity = _readOnlyDb.Entities
.Where(e => e.Id == Id)
.Select(e => new SimplPocoForEntity
{
PocoProperty1 = e.EntityProperty1,
PocoProperty2 = e.EntityProperty2,
PocoProperty3 = e.EntityProperty3
// etc.
})
.Single();
With this approach no lazy loading would happen at all because you don't load the entity but directly the PocoForEntity from the database.