EF Core 2.0 ThenInclude() navigation not reachable - c#

I'm trying to fetch entities which have children and grandchildren
The Entities are following code first conventions and are as follows
//This is the father class
public partial Class Solicitud{
[InverseProperty("Solicitud")]
public virtual ICollection<Operacion> Operaciones { get; set; }
//Other properties
}
//This is the child class
public partial Class Operacion{
[JsonIgnore] //This is so when serializing we don't get a circular reference
[InverseProperty("Operaciones")]
public virtual Solicitud Solicitud { get; set; }
public virtual Practica Practica { get; set; }
//Other Properties
}
//This is the grandchild class
public partial Class Practica
{
String Nombre;
//Other Properties
}
If I do
context.Solicitudes
.Include(w => w.Operaciones)
.Where(x => x.Profesional == profesional).OrderBy(something);
It works out ok, populating the "Operaciones" collections, and leaving the "Practica" property as null as expected.
The problem arises when I try to get the grandchildren, by use of
context.Solicitudes
.Include(w => w.Operaciones)
.ThenInclude(o => o.Practica)
.Where(x => x.Profesional == profesional);
There, it still populates Operaciones, but in each Operacion the property practica stays null, and I get the following message
warn: Microsoft.EntityFrameworkCore.Query[100106]
The Include operation for navigation '[w].Operaciones.Practica' is unnecessary and was ignored because the navigation is not reachable in the final query results. See https://go.microsoft.com/fwlink/?linkid=850303 for more information.
Which to me makes no sense because I could very well do
String something = solicitud.Operaciones.ElementAt(0).Practica.Nombre;
Is this a bug? Is there any way I can avoid using nested selects? The classes are really big in that they have a lot of Properties and it becomes difficult to mantain changes to the domain model using that approach.
Thanks.
Edit: edited title.

It seems that you need to start the query from the entity you want as a result. In your example Practica is not present in the result of your query because is nested (there is no direct path between your resulting query and Practica).
You could try to rewrite your query this way (and add a navigation property inside Practica if not already present):
context.Practicas
.Include(p => p.Operacion)
.ThenInclude(o => o.Solicitud)
.Where(p => p.Operacion.Solicitud.Profesional == profesional)
.ToList();

Well, I think this is actually a bug.
This database is running in SQL Server 2016 and migrated from an old, kind of unmaintained Visual Fox Pro database via an Integration Services package.
Somehow something went wrong while writing said package and the database ended up with rows that broke a foreign key restriction (specifically the one relating Operaciones to Practicas), once I deleted the rows that broke the restriction I ran the query again and it successfully populated every member it was supposed to.
I think this is a bug because the warning message was, in my opinion, a bit misleading. It said that I couldn't access Practica from Solicitud which is tecnically true because it could never get me any Practica as the database was broken, but not very accurate in why I couldn't get them.

Related

Entity Framework Core - Cannot remove optional nullable relationship

Using Microsoft.EntityFrameworkCore version 5.0.7 and Npgsql.EntityFrameworkCore.PostgreSQL version 5.0.7, I'm currently stuck trying to remove a relationship and have that change stored. Assume two models:
public class Banana {
public int Id { get; set; }
public Consumer? Consumer { get; set; }
}
public class Consumer {
public int Id { get; set; }
}
I'm able to assign a consumer just fine using
myBanana.Consumer = dbContext.Consumers.First(row => row.Id == 1);
dbContext.Update(myBanana);
dbContext.SaveChanges();
and that works just fine - the database is updated accordingly. However, once that is stored, trying to remove the reference again using
myBanana.Consumer = null;
dbContext.Update(myBanana);
dbContext.SaveChanges();
fails. After saving, the old value is still in the database, not null as I would expect. Interestingly, other changes to the banana are saved just fine.
I'm not sure if I'm hitting a weird issue with Nullables, or if I'm just missing something, so I'd appreciate some hints.
If you want to continue using auto-generated foreign key properties, you have to make sure that the navigations are loaded. If they are lazy-loaded by default and you don't manually load them, the property will already be null before you try to assign null, so EF can't observe a change.
Loading the navigation with .Include(banana => banana.Consumer) works, and so does loading it via dbContext.Entry(myBanana).Reference(banana => banana.Consumer).Load(). After the relevant navigation items are loaded, myBanana.Consumer = null from the example in the question works as expected.
If you have a non-tracking entity to work with (for example because it was generated by Model Binding), you can either get a tracking entity, or change the value of the auto-generated foreign key shadow property directly:
dbContext.Entry(myBanana).Property("ConsumerId").CurrentValue = null;
which also works. This may be a little bit less polished since you depend on a string as the field name to be correct, but it can be a valid option depending on the circumstances.

Entity Framework Core - Modified key on owned type

While saving changes in my database, an exception with the following message is returned:
The property 'OrderId' on entity type 'Order.CustomerDeliveryDetails#CustomerDetails' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
The database is implemented with entity framework core with a 'code first' approach. Order.CustomerDeliveryDetails is an owned type (of the type CustomerDetails) of the entity Order. CustomerDetails has no property called OrderId. As I understand OrderId is a implicit key, generated by entity framework core as a shadow property.
The classes are structured as follows:
public class Order
{
public int Id { get; set; }
public CustomerDetails CustomerDeliveryDetails { get; set; }
}
[Owned]
public class CustomerDetails
{
public string Street { get; set; }
}
The object is updated as follows:
var order = await orderContext.Orders
.Where(o => o.Id== updateOrder.Id)
.FirstOrDefaultAsync();
order.CustomerDeliveryDetails.Street = updateOrder.CustomerDeliveryDetails.Street;
await orderContext.SaveChangesAsync();
What I fail to understand is how OrderId can be modified, when it can't be accessed directly in the code.
The only thing I can think of which might cause this error, is the fact that this update is being run on a timed webjob in Azure. This is hunch is supported by the fact that the update passes the related unit tests. Could this have to do with a race condition?
Update:
I'm fairly certain the error comes from some sort of race condition. The timed webjob loads a list of orders that need to be updated every 2 minutes. The update works fine as long as the list contains less then +-100 orders, but starts to fail once this list gets longer.
The webjob is probably inable to finish updating all the orders within 2 minutes if the list gets to long.
The context is added through dependency injection as follows:
serviceProvider.AddDbContext<OrdersContext>(options => options.UseSqlServer(ctx.Configuration["ConnectionString"], sqlOptions => sqlOptions.EnableRetryOnFailure()));
My best geuss is that the context is being shared between multiple calls of the webjob, which is causing the errors.
This boils down to your database relationships.Are you using database first or code first approach? How are the models defined? Whats the relationship between Order, CustomerDetails and the CustomerDeliveryDetails tables?
Please provide the code and I will be able to help you with the solution.

Entity Framework 6: virtual collections lazy loaded even explicitly loaded on a query

I have a problem with EF6 when trying to optimize the queries. Consider this class with one collection:
public class Client
{
... a lot of properties
public virtual List<Country> Countries { get; set; }
}
As you might know, with Lazy Loading I have this n+1 problem, when EF tries to get all the Countries, for each client.
I tried to use Linq projections; for example:
return _dbContext.Clients
.Select(client => new
{
client,
client.Countries
}).ToList().Select(data =>
{
data.client.Countries = data.Countries; // Here is the problem
return data.client;
}).ToList();
Here I'm using two selects: the first for the Linq projection, so EF can create the SQL, and the second to map the result to a Client class. The reason for that is because I'm using a repository interface, which returns List<Client>.
Despite the query is generated with the Countries in it, EF still is using Lazy Loading when I try to render the whole information (the same n+1 problem). The only way to avoid this, is to remove the virtual accessor:
public class Client
{
... a lot of properties
public List<Country> Countries { get; set; }
}
The issue I have with this solution is that we still want to have this property as virtual. This optimization is only necessary for a particular part of the application, whilst on the other sections we want to have this Lazy Loading feature.
I don't know how to "inform" EF about this property, that has been already lazy-loaded via this Linq projection. Is that possible? If not, do we have any other options? The n+1 problems makes the application to take several seconds to load like 1000 rows.
Edit
Thanks for the responses. I know I can use the Include() extension to get the collections, but my problem is with some additional optimizations I need to add (I'm sorry for not posting the complete example, I thought with the Collection issue would be enough):
public class Client
{
... a lot of properties
public virtual List<Country> Countries { get; set; }
public virtual List<Action> Actions { get; set; }
public virtual List<Investment> Investments { get; set; }
public User LastUpdatedBy {
get {
if(Actions != null) {
return Actions.Last();
}
}
}
}
If I need to render the clients, the information about the last update and the number of investments (Count()), with the Include() I practically need to bring all the information from the database. However, if I use the projection like
return _dbContext.Clients
.Select(client => new
{
client,
client.Countries,
NumberOfInvestments = client.Investments.Count() // this is translated to an SQL query
LastUpdatedBy = client.Audits.OrderByDescending(m => m.Id).FirstOrDefault(),
}).ToList().Select(data =>
{
// here I map back the data
return data.client;
}).ToList();
I can reduce the query, getting only the required information (in the case of LastUpdatedBy I need to change the property to a getter/setter one, which is not a big issue, as its only used for this particular part of the application).
If I use the Select() with this approach (projection and then mapping), the Include() section is not considered by EF.
If i understand correctly you can try this
_dbContext.LazyLoading = false;
var clientWithCountres = _dbContext.Clients
.Include(c=>c.Countries)
.ToList();
This will fetch Client and only including it Countries. If you disable lazy-loading the no other collection will load from the query. Unless you are specifying a include or projection.
FYI : Projection and Include() doesn't work together see this answer
If you are projection it will bypass the include.
https://stackoverflow.com/a/7168225/1876572
don't know what you want to do, you are using lambda expression not linq, and your second select it's unnecessary.
data.client is client, data.Countries is client.Countries, so data.client.Countries = data.Countries alway true.
if you don't want lazy load Countries, use _dbContext.Clients.Include("Countries").Where() or select ().
In order to force eager loading of virtual properties you are supposed to use Include extension method.
Here is a link to MSDN https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx.
So something like this should work:
return _dbContext.Clients.Include(c=>c.Countries).ToList();
Im not 100% sure but I think your issue is that you are still maintaining a queryable for your inner collection through to the end of the query.
This queryable is lazy (because in the model it was lazy), and you havent done anything to explain that this should not be the case, you have simply projected that same lazy queryable into the result set.
I cant tell you off the top of my head what the right answer here is but I would try things around the following:
1 use a projection on the inner queriable also eg
return _dbContext.Clients
.Select(client => new
{
client,
Countries = client.Countries.Select(c=>c)// or a new Country
})
2 Put the include at the end of the query (Im pretty sure include applies to the result not the input. It definitally doesnt work if you put it before a projection) eg:
_dbContext.Clients
.Select(client => new
{
client,
client.Countries
}.Include(c=>c.Countries)`
3 Try specifying the enumeration inside the projection eg:
_dbContext.Clients
.Select(client => new
{
client,
Countries = client.Countries.AsEnumerable() //perhaps tolist if it works
}`
I do want to caviat this by saying that I havent tried any of the above but I think this will set you on the right path.
A note on lazy loading
IMO there are very few good use cases for lazy loading. It almost always causes too many queries to be generated, unless your user is following a lazy path directly on the model. Use it only with extreme caution and IMO not at all in request response (eg web) apps.

EntityFramework Not Loading Related Data

I have these entities:
public class Company : PrimaryKey
{
public string Name {get;set;}
public virtual Account Account {get;set;}
}
public class Account
{
[Key]
public Guid CompanyId { get; set; }
public virtual Company Company {get;set;}
}
I use these configurations:
modelBuilder.Entity<Company>()
.HasOptional(c => c.Account)
.WithRequired(a => a.Company)
.WillCascadeOnDelete();
Now, I have two projects, one is a test bench project which is a Console Application with a DbContext and a Repository, the second is the full blown production project which is a MVC 4 in which I use Dependancy Injection to create a Repository .InTransientScope() which in turn loads a new context each time it is called.
Both have exactly the same contexts and repositories (the product obviously has Interfaces).
in the test bench when I call this:
_repository.GetById<Company>(id);
All of it properties are filled out, i.e. eager loading
in the production when I call the same line, nothing is loaded and its not loaded till I created another function which does this:
_dbContext.Companies.Include("Account").FirstOrDefault(x => x.Id.Equals(id));
Of which, when executed does provide all the Account information, but funnily bar any other navigation properties that Account contains!!! Even though I have disable LazyLoading, it still doesn't work.
This is surprising because both projects are fundamentally the same, bar the use of the IoC DI in one of them....
Why is this happening? How can I specify in a predominantly generic Repository to eager load all this information at the Controllers preference....?
Break Points
I set break points in both projects too look at the ADO.NET call to the database and the sql statement that was executed, in the test bench it did go off and call the information, in the production it did not show any joins or anything of that nature what so ever.
Other Things Tried
I tried accessing the navigation property directly when loading it from the database:
var acc = _repository.GetById<Company>(id).Account;
It still says null. So my repository/context is not even loading any related data when asked for it.... what is going on?!
Definitions
_repository.GetById<Company>(id);
is:
public T GetById<T>(Guid id)
{
return _dbContext.Set<T>().FirstOrDefault(x => x.Id.Equals(id));
}
It's working now, I have no idea why.. I haven't tampered with anything. The only thing that I have, was to put .InTransientScope() onto IDbContextFactory<MyContext>
I actually enabled all Lazy Loading everywhere I could, and it now works.... but it's strange that when I started on the Production project I never even tampered with Lazy Loading at all, but since I extented the Model and added modelBuilder stuff I have had to specifically tell it to Lazy Load.

Linq To SQL OrderBy, issue when using enums

I am having some issues with using the OrderBy extension method on a LINQ query when it is operating on an enum type. I have created a regular DataContext using visual studio by simply dragging and dropping everything onto the designer. I have then created seperate entity models, which are simply POCO's, and I have used a repository pattern to fetch the data from my database and map them into my own entity models (or rather, I have a repository pattern, that builds up and IQueryable that'll do all this).
Everything works just fine, except when I try to apply an OrderBy (outside of the repository) on a property that I have mapped from short/smallint to an enum.
Here are the relevant code bits:
public class Campaign
{
public long Id { get; set; }
public string Name { get; set; }
....
public CampaignStatus Status { get; set; }
...
}
public enum CampaignStatus : short {
Active,
Inactive,
Todo,
Hidden
}
public class SqlCampaignRepository : ICampaignRepository
{
...
public IQueryable<Campaign> Campaigns()
{
DataContext db = new DataContext();
return from c in db.Campaigns
select new Campaign
{
Id = c.Id,
Name = c.Name,
...
Status = (CampaignStatus)c.Status,
...
};
}
}
And then elsewhere
SqlCampaignRepository rep = new SqlCampaignRepository();
var query = rep.Campaigns().OrderBy(c => c.Status);
This triggers the following exception:
System.ArgumentException was unhandled by user code
Message="The argument 'value' was the wrong type. Expected 'IQMedia.Models.CampaignType'. Actual 'System.Int16'."
Source="System.Data.Linq"
StackTrace:
ved System.Data.Linq.SqlClient.SqlOrderExpression.set_Expression(SqlExpression value)
ved System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
ved System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
ved System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitIncludeScope(SqlIncludeScope scope)
...
(sorry about the danish in there, ved = by/at).
I have tried typecasting the Status to short in the orderBy expression, but that doesn't help it, same if i cast it to the actual enum type as well.
Any help fixing this is greatly appreciated!
Can you specify the type CampaignStatus directly in your DataContext trough the designer? This way the value is automatically mapped to the enum.
What is the relationship between the Campaign class and Campaigns? If Campaigns returns the set of Campaign object, note you can't normally select new a mapped entity.
I wonder if it would work any better if you did the OrderBy before the Select?
One final trick might be to create a fake composable [Function], using trivial TSQL. For example, ABS might be enough. i.e. something like (on the context):
[Function(Name="ABS", IsComposable=true)]
public int Abs(int value)
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
Then try:
.OrderBy(x => ctx.Abs(x.Status))
I haven't tested the above, but can give it a go later... it works for some other similar cases, though.
Worth a shot...
My DataContext has it's own entity class named Campaign, (living in a different namespace, of course). Also the status column is saved as a smallint in the database, and the LINQ Entity namespace has it's type listed as a short (System.Int16).
The orderby DOES work if I apply it in the query in my repository - this is all a part of a bigger thing though, and the whole idea is to NOT have the repository applying any sort, filtering or anything like that, but merely map the database entity classes to my own ones. This example right there is obviously a bit pointless in which it's pretty much a straight mapping, but in some cases I have localization added into it as well.
Also I forgot to add - the exception obviously doesn't occour till i try to execute the query (ie - calling ToList, or enumerating over the collection).
In the bigger picture this method is being used by a service class which is then supposed to add filtering, sorting and all that - and the point of all this is of course to separate things out a bit, but also to allow easy transition to a different database, or a different OR/M, later on, if that would be the desire.
Ah didn't see that last bit till after I replied - I have not had any experience using the Function attribute yet, but I will not have access to the datacontext in the class where I am supposed to apply the sorting.

Categories

Resources