Exclude related entities even if DbContext has loaded them - c#

Is there a way I can get a list of EntityA without its related navigations, even when DbContext has them. I need this for serialization purposes.
I tried to turn Lazy loading off and explicitly .Include any related entities. But if DbContext has already loaded them, it will be included anyway.
The senario is like this:
public class LookupRepository : ILookupRepository
{
private readonly CustomDbContext _dbContext;
public LookupRepository(CustomDbContext dbContext) {
if(dbContext == null)
throw new ArgumentNullException("dbContext");
_dbContext = dbContext;
}
public IEnumerable<Country> GetCountriesFull() {
return _dbContext.Set<Country>()
.Include(c => c.Areas)
.Include(c => c.Continent)
.ToList();
}
public IEnumerable<Country> GetCountries() {
return _dbContext.Set<Country>()
.ToList();
}
public IEnumerable<Continent> GetContinents() {
return _dbContext.Set<Continent>()
.ToList();
}
public IEnumerable<Area> GetAreas() {
return _dbContext.Set<Area>()
.ToList();
}
}
And the DbContext I inject in there is initialized like this:
public CustomDbContext CreateDbContext(){
var dbContext = new CustomDbContext();
dbContext.Configuration.ProxyCreationEnabled = false;
return dbContext;
}
So this test that uses a clean DbContext passes:
[Test]
public void GetCountries_CalledOnce_ReturnsCountriesWithoutNavigations() {
var sut = CreateLookupRepository();
var countries = sut.GetCountries();
CollectionAssert.IsNotEmpty(countries);
Assert.That(countries.Select(c => c.Continent), Is.All.Null);
Assert.That(countries.Select(c => c.Areas), Is.All.Null);
}
but this one that includes all a call to GetCountriesFull fails:
[Test]
public void GetCountries_AfterCallingGetCountriesFull_StillReturnsNoNavigations() {
var sut = CreateLookupRepository();
var fullCountries = sut.GetCountriesFull();
var countries = sut.GetCountries();
CollectionAssert.IsNotEmpty(countries);
Assert.That(countries.Select(c => c.Continent), Is.All.Null);
Assert.That(countries.Select(c => c.Areas), Is.All.Null);
}
Any advice on how to do this? I thought of using a factory to create a new dbContext for each method (anyway this code is only run on my application startup and data remain in memory as Singleton), but I thought there must me a better solution.

The most simple way to do this is by getting the entities with AsNoTracking(). By doing this, you tell EF not to add the entities to its internal cache and entity relationships will not be resolved.
But here (again) the extra repository layer works against you, because you can't just do a call like
var fullCountries = sut.GetCountriesFull().AsNoTracking();
You have to make overloads, or add parameters like bool withTracking, or initialize the repository with an option to always (or never) use AsNoTracking().

If you don't care that they are being loaded unnecessarily (or they were already filled in for another reason), just set all navigational properties to null if you don't want them to be passed.
You can also add the [XmlIgnore] attribute to your navigational properties, so the serializer will not include them. That also prevents the same issue.

Dear there are many ways to doing it
1)if you are using code 1st then follow this
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.Configuration.LazyLoadingEnabled = false;
}
2)The edmx file has in the and definition an attribute for lazy loading where you can set lazy loading generally to false:
public MyEntitiesContext() : base("name=MyEntitiesContext", "MyEntitiesContext")
{
this.ContextOptions.LazyLoadingEnabled = false;
OnContextCreated();
}
or simply do this
public BlogContext() : base()
{
this.Configuration.LazyLoadingEnabled = false;
}

Related

Entity Framework: object returns with an empty list at first, but then suddenly the list is populated correctly

I have the following class:
public class User
{
public int Id { get; set; }
public List<User> Connections { get; set; }
//other properties
public User()
{
Connections = new List<User>();
}
}
Then I have a DataContext class for storage:
public class DataContext : DbContext
{
public DataContext() { }
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public virtual DbSet<User> Users { get; set; }
}
And a UserService class:
public class UserService: IUserService
{
private DataContext _context;
public UserService(DataContext context)
{
_context = context;
}
public User GetById(int id)
{
return _context.Users.Find(id);
}
...
}
Now suppose I correctly stored 2 users, and I add each other to their respective connection lists.
The problem is in the following piece of code:
var user1 = _userService.GetById(userId);
---> Here user1.Connections is an empty list (unexpected)
var results = anotherList.Select(x=>
{
---> Here user1.Connections have one object inside (the other user as expected)
});
I thought it was because the List was not populated yet since it was never accessed yet, but I also have a problem with the following endpoint in a controller:
var userId = int.Parse(User.Identity.Name);
var user1 = _userService.GetById(userId);
var connectionsInfo = user1.Connections.Select(x => new
{
Id = x.Id,
//map other properties
});
return Ok(connectionsInfo);
//this time an empty list is returned in the response, instead of a list with a single object
I read it might be regarding circular dependency, but I don't get any exception.
Also, I do not understand why in one case the list is populated after and in the other case is not populated at all.
Any idea what could be causing this?
Also I do not understand why in one case the list is populated after and in the other case is not populated at all.
It's the Lazy Loading feature in the entity framework. Lazy loading means delaying the loading of related data until you specifically request for it. For more explanation and a deep dive, you can review this good article.
Entity Framework supports three ways to load related data - eager loading, lazy loading, and explicit loading. for your scenario, It would prefer to use eager loading way. for achieving this goal EF has the Include() method. so, you can update your GetById method as below:
public User GetById(int id)
{
return _context.Users
.Include(item => item.Connections)
.Find(id);
}
With the above query when you find a specific user, its connections loads at the same time too. good luck.

When do you need to .Include related entities in Entity Framework?

This seems arbitrary to me when I have to actually .Include() related entities and when I don't. In some cases, EF gives me the info for the related entities without it and in other cases, it can't do anything with the related entities because I didn't include them:
Works without .Include();
This is an example where I'm loading data without .Include();
public class InvoiceService
{
private ApplicationDbContext db { get; set; }
public InvoiceService(ApplicationDbContext context)
{
db = context;
}
public Invoice Get(int id)
{
return db.Invoices.SingleOrDefault(x => x.Id == id);
}
}
public partial class ShowInvoice : System.Web.UI.Page
{
private InvoiceService invoiceService;
private readonly ApplicationDbContext context = new ApplicationDbContext();
protected void Page_Load(object sender, EventArgs e)
{
invoiceService = new InvoiceService(context);
if (!IsPostBack)
{
int.TryParse(Request.QueryString["invoiceId"].ToString(), out int invoiceId);
LoadInvoice(invoiceId);
}
}
private void LoadInvoice(int invoiceId)
{
var invoice = invoiceService.Get(invoiceId);
// Other code irrelevant to the question goes here.
}
}
Here follows the result which includes the data for the Company associated with the invoice I'm requested:
As you can see, the information for the company definitely comes through but was not explicitly included.
Doesn't work without .Include();
Conversely, I've done some mapping to do with invoices in this same project and I got NullReferenceExceptions when fetching the related entities property values because I didn't .Include().
This method gets all the approved timesheet entries for the specified company. This viewmodel is exclusively to be used when manipulating the association of timesheet entries for an invoice (so you're invoicing based on the timesheet entries selected).
public List<InvoiceTimesheetViewModel> GetInvoiceTimesheetsByCompanyId(int companyId)
{
var factory = new TimesheetViewModelsFactory();
var timesheets = db.Timesheets.Where(x => x.Approved && x.Company.Id == companyId && !x.Deleted).ToList();
return factory.GetInvoiceTimesheetsViewModel(timesheets);
}
NullReferenceExceptions occurred in the factory that maps the timesheet entities to the viewmodel:
public List<InvoiceTimesheetViewModel> GetInvoiceTimesheetsViewModel(List<Timesheet> timesheets)
{
var model = new List<InvoiceTimesheetViewModel>();
foreach (var timesheet in timesheets)
{
var start = DateTime.Parse((timesheet.DateAdded + timesheet.StartTime).ToString());
var finished = DateTime.Parse((timesheet.DateCompleted + timesheet.EndTime).ToString());
DateTime.TryParse(timesheet.RelevantDate.ToString(), out DateTime relevant);
model.Add(new InvoiceTimesheetViewModel
{
RelevantDate = relevant,
BillableHours = timesheet.BillableHours,
Finished = finished,
Id = timesheet.Id,
StaffMember = timesheet.StaffMember.UserName, // NRE here.
Start = start,
Task = timesheet.Task.Name // NRE here.
});
}
return model;
}
To fix these, I had to change the query that fetches the data to the following:
var timesheets = db.Timesheets.Include(i => i.StaffMember).Include(i => i.Task)
.Where(x => x.Approved && x.Company.Id == companyId && !x.Deleted).ToList();
Why is Entity Framework sometimes happy to give me data without me explicitly requesting that data and sometimes it requires me to explicitly request the data or else throws an error?
And how am I to know when I need to explicitly include the data I'm looking for and when I don't?
Entity framework uses lazy loading to load child relationships. For lazy loading to work property in the model should be marked with virtual keyword. Ef overrides it and adds lazy loading support.
When you have no virtual property EF has no way to load your child relationship data later, so the only time it's possible to do - during initial data loading using Include.
public class Timesheet
{
...
public virtual StaffMember StaffMember { get; set; }
public virtual Task Task { get; set; }
...
}
It depends on your models. If you have marked relational properties as virtual then you'll need to use .Include so EF knows that you need it. It is Lazy Loading. Preserves machine's memory and DB requests.

Projecting to domain class with constructor with EF with AsEnumerable

TL;DR
Is AsEnumerable() on an IQueryable() safer to use (as in, does it already execute as ToList()) to have a workaround (see below) for the error automapper Only parameterless constructors and initializers are supported in LINQ to Entities.?
In other words what is the effect of using AsEnumerable() on IQueryable() especially when chaining a Where() to it for example.
Please read below for full context and info.
Long Version
I'm implementing an abstraction for my repository layer, as I have to be able to read data from JSON files, XML files and also EntityFramework (database).
Problem description
I was confronted when projecting my EF entities with the error automapper Only parameterless constructors and initializers are supported in LINQ to Entities. when executing code like this:
public IEnumerable<Person> All() {
return _dataContext
.People
.Select(p => new Person(p.Id, p.FirstName, p.LastName));
}
As a reference, this is my DbContext, so you see that the above _dataContext.People returns an IQueryable<EFPerson>:
public class EFDataContext : DbContext
{
public IDbSet<EFPerson> People { get; set; }
public EFDataContext()
: this(Settings.Default.EFDataContextConnectionString) { }
public EFDataContext(string nameOrConnectionString)
: this(() => nameOrConnectionString) { }
public EFDataContext(Func<string> connectionStringProvider)
: base(connectionStringProvider()) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<EFPerson>()
.HasKey(p => p.Id)
.ToTable(Settings.Default.PeopleTable);
}
}
My solution
I didn't want to use AutoMapper, I also didn't want to make my domain entities have setters - as they needed to be immutable/read-only for the business model I'm writing.
The solution I came up with was using .AsEnumerable() and then project with the constructor of my domain entity:
public IEnumerable<Person> All() {
return _dataContext
.People
.AsEnumerable()
.Select(p => new Person(p.Id, p.FirstName, p.LastName));
}
The code runs quick, and I can also do .Where projections afterwards on the domain entity. I think this is safe as my understanding is that .AsEnumerable isn't evaluated immediately like .ToList would be.
Question Recap
My Questions thus is, is my assumption true. Is it a safe workaround to do this, or should I model it differenly - either using AutoMapper or write lengthier logic in my EntityFramework implementation of the service layer / repository?
As your question is quite wide, I'll describe my solution:
Use your domain entities in Entity Framework:
public class EFDataContext : DbContext
{
public IDbSet<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Person>()
.HasKey(p => p.Id)
.ToTable(Settings.Default.PeopleTable);
}
}
Design your entities as you need:
public class Person
{
private Person() //for EF
{
}
public Person(string name) //for me
{
Name = name;
}
public int Id { get; private set; }
public string Name { get; private set; }
public string LastName { get; private set; }
}
Query:
public IEnumerable<Person> All() {
return _dataContext
.People
.AsEnumerable();
}
Why I use AsEnumerable here? Just to hide my database and it's IQueryable.
As you can see, EF allows to work with domain objects.

Get ignored properties in Entity Framework

I work on a framework with EF. I want to get all ignored properties of an entity to build some special queries. How can I do it?
public class Customer
{
public int Id { get; set; }
public DateTime BirthDate { get; set; }
public int Age { get; set; }
}
public class CustomerContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Ignore(customer => customer.Age);
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
}
public static class DbContextExtensions
{
public static List<string> GetIgnoredProperties(this DbContext context, string entityTypeName)
{
// ???
}
}
I know this is not answering your original question, and in my comments I mentioned that you should use reflection, but that was only because I read your question wrong.
Here is an alternative using reflection, for if you do not come right.
If you assign the [NotMapped] attribute to the properties on your class that you would like to ignore, you could possibly retrieve all [NotMapped] properties using reflection. Below is an example of how this could be achieved.
var resultArray = yourClassInstance.GetType().GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(NotMappedAttribute)));
Hope this helps you in some way.
You can achieve what you want by calling the DbModelBuilder.Build. It will create a DbModel base on configuration setup by the DbModelBuilder. The DbModel expose a ConceptualModel that hold the types used by the context. The EdmModel hold each type that are declared in the context, and for each type, it hold the properties that has not been ignored by the DbModelBuilder during it's configuration. So, to achieve what you want, you have to intersect the properties of each entity type with those present in the EdmModel. It will give the delta between them, thefore the ignored properties. Here an example :
public class CustomerContext : DbContext
{
private static IReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>> _ignoredProperties;
/// Hold the ignored properties configured from fluent mapping
public static IReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>> IgnoredProperties
{
get
{
return _ignoredProperties;
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Ignore(customer => customer.Age);
// Build ignored properties only if they are not
if (_ignoredProperties == null)
{
var model = modelBuilder.Build(this.Database.Connection);
var mappedEntityTypes = new Dictionary<Type, IReadOnlyCollection<PropertyInfo>>();
foreach (var entityType in model.ConceptualModel.EntityTypes)
{
var type = Type.GetType(entityType.FullName);
var typeProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var mappedProperties = entityType.DeclaredProperties.Select(t => t.Name)
.Union(entityType.NavigationProperties.Select(t => t.Name));
mappedEntityTypes.Add(type, new ReadOnlyCollection<PropertyInfo>(
typeProperties.Where(t => !mappedProperties.Contains(t.Name)).ToList()));
}
_ignoredProperties = new ReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>>(mappedEntityTypes);
}
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
}
The IgnoreProperties property is a singleton that will be initialized the first time you will use the context. It will be null before that, so will have to ensure that nothing use it until it's initialized. It's readonly, so you don't have to worrie about accidental clear of the collection. The entity type is used as key, and the value expose a collection that hold ignored properties. Example of use :
var properties = CustomerContext.IgnoredProperties[typeof(Customer)];
Cons :
With this approach is that the DbModel will be built twice, one time to gather the ignored properties, and second time by EntityFramework when the DbCompiledModel will be cached for futur ObjectContext creation. It can have an impact on the cold start of the DbContext, it means that the fist time you will execute a query over your context, it will be a bit slower. It will depend on the size of the DbContext. Warm queries should not suffer. OnModelCreating will be called once anyway.
Pros :
All changes made on de DbModelBuilder configuration will be automatically reflected in the IgnoredProperties property.

Configure EF DAO to return detached entities

Is it possible (with attributes or something) to configure DAO methods to return detached objects? I am keen to do this, because I want to make sure that the DAO pre-fetches any fields and relationships that might be required by downstream code on return from the DAO. If the entities are detached, then an exception will be thrown and we can identity the issue easily. With lazy resolution of relationships, you potentially get multiple additional requests to the DB without realising it.
For example, let's say I have a DAO class:
public class TestDao
{
private readonly MyContext _db;
public TestDao(MyContext db)
{
_db = db;
}
public List<Group> AllGroups()
{
return _db.Groups.ToList();
}
}
And then say that I have a client of the Dao:
public void TestGetAllGroups()
{
var groups = _testDao.AllGroups();
foreach (var group in groups)
{
var x = group.Memberships;
Console.WriteLine( group.id + ":" + x.Count );
}
}
This code works, but each iteration in the test harness causes a new hit to the DB because the DB hasn't pre-fetched (included) the Memberships relationship.
I'm looking for the best way to get this code to throw an exception, saying that group.Memberships is null or something. If the Group instances were detached upon exit from TestDao.AllGroups(), then this would do the trick, and alert us to the fact that the DAO needs to include the Memberships before returning from the AllGroups() method
Looks like you can disable Lazy Loading of relationships. In my Context class:
public partial class MyContext : DbContext
{
static MyContext()
{
Database.SetInitializer<MyContext>(null);
}
public MyContext() : this("Name=MyContext")
{
}
public MyContext(string name): base(name)
{
Configuration.LazyLoadingEnabled = false;
}
...
}
However, in addition to this, I need to make sure that the EF entity constructor doesn't create empty lists for the one-to-many relations. These seem to get added by default with the schema-first entity code generation tool:
public partial class Group
{
public Group()
{
//Remove this line!
//this.Memberships = new List<Membership>();
}
public int id { get; set; }
public string name { get; set; }
public virtual ICollection<Membership> Memberships { get; set; }
}
Now, my test harness will throw a NPE unless I put a .Include into the Dao to include Memberships

Categories

Resources