I have an entity and context defined in Asp.Net Core Web API project with NRT (Nullable Reference Type) enabled as follows.
public class Inspection
{
public int Id { get; set; }
[StringLength(20)]
public string Status { get; set; } = string.Empty;
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> opts) : base(opts) { }
public DbSet<Inspection> Inspections { get; set; } = null!;
}
Visual Studio Community generates a controller and one of its endpoints is as follows.
[HttpGet]
public async Task<ActionResult<IEnumerable<Inspection>>> GetInspections()
{
if (context.Inspections == null)
{
return NotFound();
}
return await _context.Inspections.ToListAsync();
}
However, many tutorials I read and watch don't check context.Inspections for null.
In addition, I have read that ToListAsync will return an empty list if no entry found.
Question: Is it necessary to do such a check? Does EF Core guarantee that the properties of type DbSet will never be null?
No, these properties are initialized by the DbContext constructor.
The constructor uses an IDbSetInitialzer object to initialize all DbSet properties.
ServiceProviderCache.Instance.GetOrAdd(options, providerRequired: false)
.GetRequiredService<IDbSetInitializer>()
.InitializeSets(this);
Using an IDbSetInitializer like this allows replacing it with mock initializers for test purposes.
The initializer will find all DbSets on the DbContext and initialize them:
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void InitializeSets(DbContext context)
{
foreach (var setInfo in _setFinder.FindSets(context.GetType()).Where(p => p.Setter != null))
{
setInfo.Setter!.SetClrValue(
context,
((IDbSetCache)context).GetOrAddSet(_setSource, setInfo.Type));
}
}
Before EF Core 6, only entity sets specified through DbSet<> properties were cached, which resulted in performance gains over direct calls to Set<T>()
Related
I got an error using ASP.NET Identity in my app.
Multiple object sets per type are not supported. The object sets
'Identity Users' and 'Users' can both contain instances of type
'Recommendation Platform.Models.ApplicationUser'.
I saw a few questions about this error in StackOverflow. All indicate on two DbSet objects of the same type. But in my DbContext there aren't the same types of DbSets. Exception is thrown on FindAsync() method during logging in.
if (ModelState.IsValid)
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null && user.IsConfirmed)
{
The problem is I don't have two DbSets of the same type. My Contexts look like this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public System.Data.Entity.DbSet<RecommendationPlatform.Models.ApplicationUser> IdentityUsers { get; set; }
}
and
public class RecContext : DbContext
{
public RecContext()
: base("RecConnection")
{
Database.SetInitializer<RecContext>(new DropCreateDatabaseIfModelChanges<RecContext>());
}
public DbSet<Recommendation> Recommendations { get; set; }
public DbSet<Geolocation> Geolocations { get; set; }
public DbSet<Faq> Faqs { get; set; }
public DbSet<IndexText> IndexTexts { get; set; }
}
What could cause this problem? Maybe something connected with in-built ASP.NET Identity functionalities? Anyway, what is Users type? I don't have it in my app...
You do have two DbSets` of the same type.
IdentityDbContext<T> itself contains Users property declared as:
public DbSet<T> Users { get; set; }
You're declaring second one in your class.
review this file "ApplicationDbContext.cs", remove the line, generated automatically by scaffold last, should be like this:
public System.Data.Entity.DbSet<Manager.Models.ApplicationUser> IdentityUsers { get; set; }
This issue can arise from using scaffolding to create a View. You probably did something like this: View > Add > New Scaffold Item... > MVC 5 View > [Model class: ApplicationUser].
The scaffolding wizard added a new line of code in your ApplicationDbContext class.
public System.Data.Entity.DbSet<RecommendationPlatform.Models.ApplicationUser> IdentityUsers { get; set; }
Now you have two DbSet properties of the same type which not only causes an exeptions to be thrown in the FindAsync() method but also when you try to use code-first migrations.
Be very careful when using scaffolding or even better don't use it.
Comment the new generated Dbset from identity model class like below
// public System.Data.Entity.DbSet<SurveyTool.Models.ApplicationUser> ApplicationUsers { get; set; }
Whenever I see this problem, I always double check the DbSet. - ESPECIALLY if you are using another language for Visual Studio.
For us who use other language on VS, always double check because the program doesn´t create controllers or models with the exact name. perhaps this should be a thread.. or there is one already and I missed it.
i have maintened a big web system built some years ago and it is already in production (i am saying this to to emphasize the complexity of tinkering with the system structure).
Till now this system has been working with physical exclusion of database records, however now there is a necessity to change it to logical exclusion (updating the status (to "D") of a register in table).
My entities are mapped by FluentApi.
My properties in context are built using DbSet, example:
DbSet Person {get;set;}
I would like to know a way to only fetch records in my entity with status != "D" straight from entity mapping or through an overall filter that always executes when i call my entity in context.
I have tried a solution like suggested here:
http://patrickdesjardins.com/blog/using-a-filtereddbset-with-entity-framework-to-have-dynamic-filtering
It actually it works fine to fetch the data i want however, i cannot use the methods to change the record like "AddOrUpdate" because when i use that solution my property needs to be a IDbSet and not a DbSet.
Example:
To use the solution suggested in the above link the property must be changed from this:
DbSet Person { get; set; }
to this:
IDbSet Person { get; set; }
Sample code:
// My Entity
public class Person
{
public int Id {get; set;}
public string Name {get; set;}
public string Status {get; set;}
public ICollection<Address> PersonAddress {get; set;}
}
// My Context example
public class MyContext : DbContext
{
public MyContext(System.Data.Common.DbConnection conn, bool contextOwnsConnection) : base(conn, contextOwnsConnection)
{
ConnectionString = conn.ConnectionString;
Database.SetInitializer<MyContext>(null);
SetDatabaseSettings(); //NLS and regional configurations
//Here i am calling the method built in the link above
this.Person = new FilteredDbSet<Person>(this, a => !a.Status.Equals("D"));
}
// This is now a IDbSet type
public IDbSet<Person> Persons { get; set; }
// The others are DbSet type
public DbSet<Address> Adressess { get; set; }
}
// Any use of context
public class PersonsClass{
private readonly MyContext context;
public PersonsClass(MyContext context)
{
this.contexto = context;
}
// Considering the filter applied in MyContext constructor method
// this method returns all "person" with status property != D (OK)
public List<Person> ListAllPersons()
{
return context.Person.toList();
}
// When try to perform add or update method, the system throws the following exception:
// "Unable to call public, instance method AddOrUpdate on derived IDbSet type 'Context.FilteredDbSet`1[Entities.Person]'. Method not found."
// I believe that this is happening because "AddOrUpdate" method belongs to DbSet class instead of IDbSet interface.
public void UpdatePerson(Person newPerson)
{
context.Person.AddOrUpdate(newPerson);
}
}
So, when i try to change a record of this entity the exception: "Unable to call public, instance method AddOrUpdate on derived IDbSet type 'Context.FilteredDbSet`1[Entities.Person]'. Method not found." is thrown.
Any tips about how to go through it?
I have some models like those below:
public class Mutant
{
public long Id { get; set; }
...
// Relations
public long OriginalCodeId { get; set; }
public virtual OriginalCode OriginalCode { get; set; }
public int DifficultyLevelId { get; set; }
public virtual DifficultyLevel DifficultyLevel { get; set; }
}
and
public class OriginalCode
{
public long Id { get; set; }
...
// Relations
public virtual List<Mutant> Mutants { get; set; }
public virtual List<OriginalCodeInputParameter> OriginalCodeInputParameters { get; set; }
}
and in the OnModelCreating of DBContext I made the relations like these:
modelBuilder.Entity<Mutant>()
.HasOne(m => m.OriginalCode)
.WithMany(oc => oc.Mutants)
.HasForeignKey(m => m.OriginalCodeId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
modelBuilder.Entity<Mutant>()
.HasOne(m => m.DifficultyLevel)
.WithMany(dl => dl.Mutants)
.HasForeignKey(m => m.DifficultyLevelId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
now when I request for Mutants, the OriginalCode is null:
but as soon as I request for OriginalCodes like below:
then the OriginalCode field of the mutants will be not null:
What is the reason and how could I fix it?
The reason is explained in the Loading Related Data section of the EF Core documentation.
The first behavior is because EF Core currently does not support lazy loading, so normally you'll get null for navigation properties until you specifically load them via eager or explicit loading. However, the Eager loading section contains the following:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
which explains why the navigation property is not null in the second case.
Now, I'm not sure which of the two behaviors do you want to fix, so will try to address both.
The first behavior can be "fixed" by using one of the currently available methods for loading related data, for instance eager loading:
var mutants = db.Mutants.Include(m => m.OriginalCode).ToList();
The second behavior is "by design" and cannot be controlled. If you want to avoid it, make sure to use fresh new DbContext instance just for executing a single query to retrieve the data needed, or use no tracking query.
Update: Starting with v2.1, EF Core supports Lazy Loading. However it's not enabled by default, so in order to utilize it one should mark all navigation properties virtual, install Microsoft.EntityFrameworkCore.Proxies and enable it via UseLazyLoadingProxies call, or utilize Lazy-loading without proxies - both explained with examples in the EF Core documentation.
Using Package Manager Console install Microsoft.EntityFrameworkCore.Proxies
install-package Microsoft.EntityFrameworkCore.Proxies
And then in your Context class add .UseLazyLoadingProxies():
namespace SomeAPI.EFModels
{
public partial class SomeContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer(connectionString);
}
}
}
}
I got an error using ASP.NET Identity in my app.
Multiple object sets per type are not supported. The object sets
'Identity Users' and 'Users' can both contain instances of type
'Recommendation Platform.Models.ApplicationUser'.
I saw a few questions about this error in StackOverflow. All indicate on two DbSet objects of the same type. But in my DbContext there aren't the same types of DbSets. Exception is thrown on FindAsync() method during logging in.
if (ModelState.IsValid)
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null && user.IsConfirmed)
{
The problem is I don't have two DbSets of the same type. My Contexts look like this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public System.Data.Entity.DbSet<RecommendationPlatform.Models.ApplicationUser> IdentityUsers { get; set; }
}
and
public class RecContext : DbContext
{
public RecContext()
: base("RecConnection")
{
Database.SetInitializer<RecContext>(new DropCreateDatabaseIfModelChanges<RecContext>());
}
public DbSet<Recommendation> Recommendations { get; set; }
public DbSet<Geolocation> Geolocations { get; set; }
public DbSet<Faq> Faqs { get; set; }
public DbSet<IndexText> IndexTexts { get; set; }
}
What could cause this problem? Maybe something connected with in-built ASP.NET Identity functionalities? Anyway, what is Users type? I don't have it in my app...
You do have two DbSets` of the same type.
IdentityDbContext<T> itself contains Users property declared as:
public DbSet<T> Users { get; set; }
You're declaring second one in your class.
review this file "ApplicationDbContext.cs", remove the line, generated automatically by scaffold last, should be like this:
public System.Data.Entity.DbSet<Manager.Models.ApplicationUser> IdentityUsers { get; set; }
This issue can arise from using scaffolding to create a View. You probably did something like this: View > Add > New Scaffold Item... > MVC 5 View > [Model class: ApplicationUser].
The scaffolding wizard added a new line of code in your ApplicationDbContext class.
public System.Data.Entity.DbSet<RecommendationPlatform.Models.ApplicationUser> IdentityUsers { get; set; }
Now you have two DbSet properties of the same type which not only causes an exeptions to be thrown in the FindAsync() method but also when you try to use code-first migrations.
Be very careful when using scaffolding or even better don't use it.
Comment the new generated Dbset from identity model class like below
// public System.Data.Entity.DbSet<SurveyTool.Models.ApplicationUser> ApplicationUsers { get; set; }
Whenever I see this problem, I always double check the DbSet. - ESPECIALLY if you are using another language for Visual Studio.
For us who use other language on VS, always double check because the program doesn´t create controllers or models with the exact name. perhaps this should be a thread.. or there is one already and I missed it.
I updated my app from EF4.1 to EF6 and now I've got lazy loading issue. I used EF6.x DbContext Generator to generate new DbContext. All of the advices from this article are also applied.
My classes are public
Not sealed, not abstract
Have a public constructor
Don't implement neither IEntityWithChangeTracker nor IEntityWithRelationships
Both ProxyCreationEnabled and LazyLoadingEnabled are set to true
Navigation properties are virtual
What also looks wierd to me is that if I explicitly include navigation property with Include("...") it gets loaded.
Simplified version of my POCOs and DbContext:
public partial class Ideation
{
public Ideation()
{
}
public long Id { get; set; }
public Nullable<long> ChallengeId { get; set; }
public virtual Challenge Challenge { get; set; }
}
public partial class Challenge
{
public Challenge()
{
this.Ideations = new HashSet<Ideation>();
}
public long Id { get; set; }
public virtual ICollection<Ideation> Ideations { get; set; }
}
public partial class BoxEntities : DbContext
{
public TIBoxEntities()
: base("name=BoxEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Ideation> Ideations { get; set; }
public virtual DbSet<Challenge> Challenges { get; set; }
}
Also I tried to set ProxyCreationEnabled and LazyLoadingEnabled explicitly without no luck. The entity isn't loaded as a dynamic proxy as this debug session screenshot shows:
What else am I missing?
A situation where this could happen is that the entity you are trying to load with Find is already attached to the context as a non-proxy object. For example:
using (var context = new MyContext())
{
var ideation = new Ideation { Id = 1 }; // this is NOT a proxy
context.Ideations.Attach(ideation);
// other stuff maybe ...
var anotherIdeation = context.Ideations.Find(1);
}
anotherIdeation will be the non-proxy that is already attached and it is not capable of lazy loading. It even wouldn't help to run a DB query with var anotherIdeation = context.Ideations.SingleOrDefault(i => i.Id == 1); because the default merge option for queries is AppendOnly, i.e. the new entity would only be added if there isn't already an attached entity with that key. So, anotherIdeation would still be a non-proxy.
You can check if the entity is already attached by using Local before you call Find in your GetById method:
bool isIdeationAttached = context.Ideations.Local.Any(i => i.Id == id);
Per #ken2k's comment, the default for new models starting with EF 4 was to enable lazy loading by default. With EF 1, it was not allowed. If you migrated your model from 1 to 4, it is kept off by default. You would need to modify the context to turn that on.
That being said, you indicate through debugging that it is true. In that case, check your usage scenarios. Is it possible your context has been disposed prior to fetching the child objects. Typically we see this when data binding to the LINQ query where the context is configured in a Using block and the actual iteration doesn't happen until after the using block's scope has passed.