Supposing I have three EF entity classes:
public class Person {
...
public ICollection Vehicles { get; set; }
}
public class Vehicle {
...
public Person Owner { get; set; }
public CarModel ModelInfo { get; set; }
}
public class CarModel {
...
// properties for make, model, color, etc
}
The Person.Vehicles property is lazy-loaded.
Supposing I have a Person instance already loaded and I want to load its Vehicle collection property such that it also includes the related ModelInfo property.
So I have this:
void LoadVehiclesAndRelated(MyDbContext dbContext, Person person)
{
dbContext.Entry( person )
.Collection( p => p.Vehicles )
.Query()
.Include( v => v.ModelInfo )
.Load();
}
Used like so:
using( MyDbContext dbContext = ... ) {
Person p = GetPerson( 123 );
LoadVehiclesAndRelated( dbContext, p );
}
foreach(Vehicle v in p.Vehicles) {
Console.WriteLine( v.ModelInfo );
}
However when I do this, I get an exception at runtime when it first evaluates the p.Vehicles expression because the property is actually empty (so it wants to load it) but the DbContext is now disposed.
When the .Load() call was made (inside LoadVehiclesAndRelated() I saw the SQL being executed against the server (in SQL Server Profiler) but the collection property remains empty.
How can I then load the property and with the Included sub-properties?
Annoyingly, this scenario is not mentioned in the MSDN guide for explicit-loading: https://msdn.microsoft.com/en-us/data/jj574232.aspx
Looks like calling .Query().Load() is not the same as calling DbCollectionEntry.Load directly, and the important difference is that the former does not set the IsLoaded property, which then is causing triggering lazy load later. Most likely because as explained in the link, the former is intended to be used for filtered (partial) collection load scenarios.
Shortly, to fix the issue, just set IsLoaded to true after loading the collection:
var entry = dbContext.Entry(person);
var vehicleCollection = entry.Collection(p => p.Vehicles);
vehicleCollection.Query()
.Include( v => v.ModelInfo )
.Load();
vehicleCollection.IsLoaded = true;
P.S. For the sake of correctness, this behavior is sort of mentioned at the end of the Applying filters when explicitly loading related entities section:
When using the Query method it is usually best to turn off lazy loading for the navigation property. This is because otherwise the entire collection may get loaded automatically by the lazy loading mechanism either before or after the filtered query has been executed.
Related
I have two data models
ApplicationUser and Team
public class Team : OwnerEntity
{
public virtual ICollection<ApplicationUser> Members { get; set; } = new HashSet<ApplicationUser>();
public int MaxTeamSize { get; set; }
public string TeamName { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public string AvatarUri { get; set; }
public Team(string teamName, int teamSize)
{
TeamName = teamName;
MaxTeamSize = teamSize;
}
public Team()
{
}
}
public class ApplicationUser : IdentityUser
{
public virtual ICollection<Team> Teams { get; set; } = new HashSet<Team>();
[ForeignKey("ActiveTeamId")]
public string ActiveTeamId { get; set; }
public virtual Team ActiveTeam { get; set; }
}
I noticed behaviour that I cannot understand when I use
var teams = db.Teams.Single();
I get the team without Members, and that is correct. However when I use
await db.Teams.SingleOrDefaultAsync(_ => _.Id == UserTeamId);
It always returns Members event though I did not include it anywhere with
db.Teams.Inlclude(_ => _.Members)
Also calling one after another
public async Task<IActionResult> TEst()
{
var teams = db.Teams.Single();
var querySingle = await db.Teams.SingleOrDefaultAsync(_ => _.Id == UserTeamId);
return new JsonResult(querySingle);
}
Results in teams.Single() also returns a list of members. I think I do not fully understand LazyLoading mechanism. Can someone explain this case?
With EF Core you need to enable lazy loading proxies before lazy loading will function. The easiest way to observe lazy load calls is with a profiler running against a test DB that is not being accessed by anything but your code under test.
If lazy loading is not enabled, EF's behaviour will associate any tracked entities it has references to. For example, given a Parent (ID 1) with 3 Children (IDs 1, 2, & 3) If at some point we load the children:
var children = context.Children.Where(x => x.ParentId == 1);
... then later load the parent:
var parent = context.Parents.Single(x => x.ParentId == 1);
var count = parent.Children.Count; // returns 3.
... you can access parent.Children and you will receive the 3 children even though you did not eager load them. The DbContext was tracking the references so it populated them. This can be rather dangerous situationally because take the following example:
var child = context.Children.Single(x => x.ChildId == 3);
var parent = context.Parents.Single(x => x.ParentId == 1);
var count = parent.Children.Count; // returns 1.
parent.Children will be populated here, but will only contain Child #3 if that is the only child the DbContext was tracking.
I would highly suspect that lazy loading is not actually enabled in your project. To test whether lazy loading is actually working, I would instantiate a new DbContext with the following check, watching a profiler for SQL hits:
using (var context = new AppDbContext()) // Your app context here.
{
var parent = context.Parents.Single(x => x.ParentId == 1); // load a single parent.
var count = parent.Children.Count; // <- breakpoint on this line.
}
Hit the breakpoint and watch the profiler. After you step over the line, did an SQL query get executed? Note even if you hover over Children in the debugger, this would trigger the lazy load proxy in most cases. (or generate a warning that threads need to execute) If count comes back with the # of children then that confirms lazy loading is configured properly. If it comes back as "0" then lazy loading isn't enabled.
For EF Core you need to include the EntityFrameworkCore.Proxies NuGet package and initialize the optionsBuilder in the DbContext .OnConfiguring handler to enable lazy loading proxies:
optionsBuilder.UseLazyLoadingProxies();
Then the tracked references for the Children collection are ignored and a lazy load call will be made.
var child = context.Children.Single(x => x.ChildId == 3);
var parent = context.Parents.Single(x => x.ParentId == 1);
var count = parent.Children.Count; // returns 3. Lazy load SQL call observed.
I tried this out with both Sync and Async calls and there was no observed difference. I suspect the behaviour you were seeing was that lazy loading isn't actually enabled and you are making an async call sometime after the DbContext has already loaded the associated children. That a call to the DbContext can return children without eager loading sometimes and not others can be a rather confusing and bug-prone behaviour. If lazy loading is configured correctly and active it should return more reliable results but I treat it more as a failsafe rather than designing for it. It's generally always better to leverage eager loading and projection to load data rather than relying on lazy loading.
So I partially followed from an SO answer on how to store a property with array datatype in Entity Framework. What I didn't follow on that answer is setting the string InternalData to be private instead of public as I find it a code smell if it is set to public (not enough reputation to comment there yet).
I also managed to map the private property in entity framework from this blog.
When I perform CR (create, read) from that entity, all goes well. However, when my LINQ query has a where clause using that property with array datatype, it says that "System.NotSupportedException: 'The specified type member is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.'".
How to work around on this? Here are the relevant code blocks:
public class ReminderSettings
{
[Key]
public string UserID { get; set; }
[Column("RemindForPaymentStatus")]
private string _remindForPaymentStatusCSV { get; set; }
private Status[] _remindForPaymentStatus;
[NotMapped]
public Status[] RemindForPaymentStatus
{
get
{
return Array.ConvertAll(_remindForPaymentStatusCSV.Split(','), e => (Status)Enum.Parse(typeof(Status), e));
}
set
{
_remindForPaymentStatus = value;
_remindForPaymentStatusCSV = String.Join(",", _remindForPaymentStatus.Select(x => x.ToString()).ToArray());
}
}
public static readonly Expression<Func<ReminderSettings, string>> RemindForPaymentStatusExpression = p => p._remindForPaymentStatusCSV;
}
public enum Status
{
NotPaid = 0,
PartiallyPaid = 1,
FullyPaid = 2,
Overpaid = 3
}
protected override void OnModelCreating(DbModelBuuilder modelBuilder)
{
modelBuilder.Entity<ReminderSettings>().Property(ReminderSettings.RemindForPaymentStatusExpression);
}
//This query will cause the error
public IEnumerable<ReminderSettings> GetReminderSettingsByPaymentStatus(Status[] statusArray)
{
var query = ApplicationDbContext.ReminderSettings.Where(x => x.RemindForPaymentStatus.Intersect(statusArray).Any());
return query.ToList(); //System.NotSupportedException: 'The specified type member 'RemindForPaymentStatus' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.'
}
Entity Framework can not translate LINQ expressions to SQL if they access a property that is annotated as [NotMapped]. (It also can not translate if the property contains custom C# code in its getter/setter).
As a quick (but potentially low performance) workaround, you can execute the part of the query that does not cause problems, then apply additional filtering in-memory.
// execute query on DB server and fetch items into memory
var reminders = dbContext.ReminderSettings.ToList();
// now that we work in-memory, LINQ does not need to translate our custom code to SQL anymore
var filtered = reminders.Where(r => r.RemindForPaymentStatus.Contains(Status.NotPaid));
If this causes performance problems, you have to make the backing field of your NotMapped property public and work directly with it.
var filtered = dbContext.ReminderSettings
.Where(r => r._remindForPaymentStatusCSV.Contains(Status.NotPaid.ToString("D"));
Edit
To handle multiple Status as query parameters, you can attach Where clauses in a loop (which behaves like an AND). This works as long as your Status enum values are distinguishable (i.e. there is no Status "11" if there is also a Status "1").
var query = dbContext.ReminderSettings.Select(r => r);
foreach(var statusParam in queryParams.Status) {
var statusString = statusParam.ToString("D");
query = query.Where(r => r._remindForPaymentStatusCSV.Contains(statusString));
}
var result = query.ToArray();
I am using Service Stack as my system's API and I'm using Entity Framework to get data from my SQL Server DataBase. Although, I cannot retrieve any data from a list of objects generated by entity framework.
[Route("/getInterventions","GET")]
public class GetInterventions
{
}
public class GetInterventionsResponse
{
public List<Intervention> interventions { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
public class GetInterventionsService : Service
{
public object Any(GetInterventions request)
{
using (var dbConnection = new operationsContext())
{
List<Intervention> dbItems = dbConnection.Interventions.ToList();
return new GetInterventionsResponse{
interventions = dbItems
};
}
}
}
From the client side I get:
The ObjectContext instance has been disposed and can no longer be used for "operations"(name of db) that require a connection.
So with this error I can verify that the problem as to do with the list that acts like a "virtual" list, and its objects are not returned to the client side, but are passed as a reference or something like that.
So how can I deep copy this list and retrieve a clone of it?
Thanks anyways
It looks like the list is no longer accessible when the context gets disposed, possibly because the variable was defined within the scope of the context. Try defining dbItems outside of the using statement:
public object Any(GetInterventions request)
{
List<Intervention> dbItems;
using (var dbConnection = new operationsContext())
{
dbItems = dbConnection.Interventions.ToList();
}
return new GetInterventionsResponse{
interventions = dbItems
};
}
Also, you may run into this issue if you are expecting navigation properties of Interventions to be loaded, which they will not be with your code because EF uses lazy loading. For example, if Intervention has a Person navigation property, you would need to include that to have it be available. Like this:
dbItems = dbConnection.Interventions.Include(x => x.Persons).ToList();
Edit based on comment below:
You can also have includes multiple levels deep like this:
dbItems = dbConnection.Interventions.Include(x => x.Persons.Car).ToList();
or for a nested list...
dbItems = dbConnection.Interventions.Include(x => x.Persons.Select(y => y.Cars).ToList();
or for multiple navigation properties...
dbItems = dbConnection.Interventions.Include(x => x.Persons)
.Include(x => x.Cars).ToList();
Here's the situation in its most simplified form using the EF5 Code-First approach:
public abstract class EntityBase<PK>
{
public PK ID { get; set; }
}
public class Country : EntityBase<string>
{
public string Name { get; set; }
}
public class Address : EntityBase<int>
{
[Required]
public string CountryID { get; set; }
public Country Country { get; set; }
// ... other address properties ...
}
The one-to-many relationship between Address and Country is set up with no cascade-delete like so:
modelBuilder.Entity<Address>()
.HasRequired(a => a.Country)
.WithMany()
.HasForeignKey(a => a.CountryID)
.WillCascadeOnDelete(false);
Finally, I have a generic base repository class with CRUD methods that call SaveChanges on the underlying DbContext to commit data changes atomically. E.g.:
public class EFRepository<T, PK> : IRepository<T, PK> where T : EntityBase<PK>
{
//
// ... other methods ...
//
public virtual void Delete(T instance)
{
// ... trigger validations, write to log, etc...
_dbContext.Set<T>().Remove(instance);
try
{
_dbContext.SaveChanges();
}
catch(Exception ex)
{
// ... handle the error ...
}
}
}
Part 1:
Scenario:
var countryRepo = new EFRepository<Country>();
var country = countryRepo.Save(new Country() { ID="??", Name="Test Country" });
var addressRepo = new EFRepository<Address>();
var address = addressRepo.Save(new Address() { Country=country });
countryRepo.Delete(country);
This should fail due to the existence of a dependent Address. However, afterwards the address ends up with a null in CountryID, which is invalid because Address.CountryID is required, so subsequent SaveChanges calls throw a validation exception unless the address is detached.
I expected that when an object is deleted, EF5 will be smart enough to first check for any cascade-delete constraints like the one above and, failing to find any, then proceed to delete the data. But exactly the opposite seems to be the case.
Is this a normal behaviour or am I doing something wrong?
Part 2:
Following a failed SaveChanges call, some Addresses are now in an invalid state in my DbContext and need to be restored to their original values. Of course, I can always do so explicitly for each entity type (Country, State, Order, etc.) by creating specialized repository classes and overriding Delete, but it smells big time. I'd much rather write some general purpose code to gracefully recover related entities after a failed SaveChanges call.
It would require interrogating DbContext to get all relationships in which an entity (e.g. Country) is the principal, regardless of whether or not its class defines navigational properties to dependent entities.
E.g. Country has no Addresses property, so I need to somehow find in DbContext the definition of the one-to-many relationship between Country and Address and use it to restore all related Addresses to their original values.
Is this possible?
Answering my own question in Part 2:
Here is my approach to checking for related dependents when deleting an entity on the principal end of a many-to-one relationship and where dependents are NOT exposed as a navigation collection in the principal (e.g. class Address has a Country property, but class Country doesn't have an Addresses collection).
DbContext
Add the following method to the context class:
/// <summary>
/// Returns an array of entities tracked by the
/// context that satisfy the filter criteria.
/// </summary>
public DbEntityEntry[] GetTrackedEntities<T>(
Expression<Func<DbEntityEntry<T>, bool>> filterCriteria)
where T : class
{
var result = new List<DbEntityEntry>();
var doesItMatch = filterCriteria.Compile();
foreach (var entry in this.ChangeTracker.Entries<T>())
{
if (doesItMatch(entry))
result.Add(entry);
}
return result.ToArray();
}
Repositories
Create a repository for each class that has some dependencies, override the Delete method and use the new GetTrackedEntities<T> method to get all related dependents and either:
explicitly delete them if they are cascade-deletable in code
detach them from the context if they are cascade-deletable in the DB itself
throw an exception if they are NOT cascade-deletable.
Example of the latter case:
public class EFCountryRepository :
EFReadWriteRepository<Country, string>,
ICountryRepository
{
public override void Delete(Country instance)
{
// Allow the Country to be deleted only if there are no dependent entities
// currently in the context that are NOT cascade-deletable.
if (
// are there any Regions in the context that belong to this Country?
_dbContext.GetTrackedEntities<Region>(e =>
e.Entity.CountryID == instance.ID ||
e.Entity.Country == instance).Length > 0
||
// are there any Addresses in the context that belong to this Country?
_dbContext.GetTrackedEntities<Address>(e =>
e.Entity.CountryID == instance.ID ||
e.Entity.Country == instance).Length > 0
)
throw new Exception(String.Format(
"Country '{0}' is in use and cannot be deleted.", instance.ID));
base.Delete(instance);
}
// ... other methods ...
}
Example of a case where cascade-deleting will be done by the DB itself, so all we need to do is detach the dependents from the context:
public class EFOrderRepository :
EFReadWriteRepository<Order, string>,
IOrderRepository
{
public override void Delete(Order instance)
{
foreach (var orderItem in _dbContext.GetTrackedEntities<OrderItem>(e =>
e.Entity.OrderID == instance.ID ||
e.Entity.Order == instance))
{
_dbContext.Entry(orderItem).State = System.Data.EntityState.Detached;
}
base.Delete(instance);
}
// ... other methods ...
}
Hope someone will find this solution helpful.
Background
I am creating a projection from a parent/child relationship that includes a Name property of the parent and a list of the children's Ids.
Code
private class ParentChildInfo
{
public string Name { get; set; }
public List<int> ChildIds { get; set; }
}
var infos = ctx.Masters.Include(m => m.Children).Select(
m => new ParentChildInfo()
{
Name = m.Name,
ChildIds = m.Children.Where(c => c.SomeProp.StartsWith("SpecialValue"))
.Select(c => c.Id).ToList()
}).ToList();
Unfortunately that produced the error
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[System.Int32] ToList[Int32]
That lead me to this post, which suggested (in the comments) making the following changes:
private class ParentChildInfo
{
public string Name { get; set; }
public IEnumerable<int> ChildIds { get; set; } // No longer List<int>
}
var infos = ctx.Masters.Include(m => m.Children).Select(
m => new ParentChildInfo()
{
Name = m.Name,
ChildIds = m.Children.Where(c => c.SomeProp.StartsWith("SpecialValue"))
.Select(c => c.Id) // Dropped the .ToList()
}).ToList();
I originally wanted to get lists rather than enumerables because the code that uses the result runs for several minutes, and I did not want to tie up the DbContext that long.
I use the code like this:
using (MyContext ctx = new MyContext())
{
// code from above that populates infoes
}
foreach (var info in infoes)
{
// use info.ChildIds
}
I planned to move the foreach into the using so that I can enumerate the ChildIds, but hit F5 instead and was surprised to see that the code works.
Question
Given that the DbContext is disposed at that point and ChildIds is an IEnumerable<int> rather than List<int>, why exactly can I enumerate ChildIds?
It is because the ToList() of the infos query actually executes the query. So the collection ctx.Masters is enumerated and the projections are populated. Even without the Include it would notice that Master.Children is addressed and emit the SQL join. The implementing type of IEnumerable<int> ChildIds is probably List<int>.
You did .ToList() on the query so the query was executed and all the results are materialized and the connection to the database should be closed. I assume it would not work if you did not have .ToList() since (at least in EF5) the results are being processed in streaming fashion and entities are materialized when requested (i.e. on each iteration in the loop).