In my ASP.NET Core Web API application, I am having trouble relating entities in Entity Framework Core. When I send a GET request to the Controller through Postman all of the Model's records are returned, but the navigation properties for their related entities come back null. Unfortunately, there are no errors or exceptions thrown either. The (simplified) code is as follows:
//DBContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Location>();
modelBuilder.Entity<Person>(entity =>
{
entity.HasOne(p => p.Location)
.WithMany(l => l.People)
.HasForeignKey(p => p.Location_ID);
});
}
//Person Model
public partial class Person
{
[Key]
public int People_ID { get; set; }
public int? Location_ID { get; set; }
public Location Location { get; set; }
}
//Location Model
public partial class Location
{
public Location()
{
People = new HashSet<Person>();
}
[Key]
public int Location_ID { get; set; }
public ICollection<Person> People { get; set; }
}
As far as I understand it, because I dictated the two Models are related using the Fluent API in the OnModelCreating method, the properties should be 'Eagerly Loaded'. Why is this not happening?
As far as I understand it, because I dictated the two Models are related using the Fluent API in the OnModelCreating method, the properties should be 'Eagerly Loaded'
You understand it wrong. The Eager Loading behavior is not implicit (like lazy loading), you have to explicitly request it using Include / ThenInclude methods.
For instance, the following will return the Person entities with Location property populated:
return db.Set<Person>().Include(p => p.Location);
Related
EDIT: The original problem statement included only one reference to the Owned entity. But it turns out that the problem I encountered happens with multiple references to the Owned entity AND some funky fluent configuration code. I've rewritten this problem statement to explain the details of the problem I ran into.
I started with one EF Core entity, which references an Owned entity:
public class InvoiceItem
{
public SubscriptionPlanDetails SubscriptionPlan { get; set; }
}
[Owned]
public class SubscriptionPlanDetails
{
public string PlanName { get; set; } // Plan name
public decimal Price { get; set; } // Price (USD)
}
When I enter the command "add-migration" in the VS Package Manager Console, scaffolder complains:
No type was specified for the decimal column 'Price' on entity type 'SubscriptionPlanDetails'.
So I added some fluent API configuration:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<SubscriptionPlanDetails>()
.Property(p => p.Price)
.HasColumnType("money");
}
Problem solved... or so I thought. Then I added another entity with a reference to the same Owned entity:
public class Account
{
public int Id { get; set; }
public SubscriptionPlanDetails SubscriptionPlan { get; set; }
// Navigation properties
public virtual ICollection<InvoiceItem> InvoiceItems { get; set; }
}
So, each account has one subscription plan (Account.SubscriptionPlan), and each account has multiple InvoiceItems, each of which contains all of the details of the subscription plan that was in effect at the time the invoice item was created (Account.InvoiceItems.SubscriptionPlan).
Now, when I try to add a migration, it complains
No type was specified for the decimal column 'Price' on entity type 'InvoiceItem.SubscriptionPlan#SubscriptionPlanDetails'.
So I added fluent API code to configure the Owned property. But I neglected to remove the previous code. So, my configuration code looked like this:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// This should have been removed!
builder.Entity<SubscriptionPlanDetails>()
.Property(p => p.Price)
.HasColumnType("money");
builder.Entity<Account>().OwnsOne(m => m.SubscriptionPlan)
.Property(p => p.Price)
.HasColumnType("money");
builder.Entity<InvoiceItem>().OwnsOne(m => m.SubscriptionPlan)
.Property(p => p.Price)
.HasColumnType("money");
}
Attempting to add a migration gets me a NullReferenceException.
If I comment out the offending call to builder.Entity<SubscriptionPlanDetails>() then it works as expected!
I think the best way to override this decimal warning is to annotate the price with the following:
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
You can also use the type "money" instead of "decimal(18,2)"
I have two code first entities, Package and PackageEntry that I am having trouble setting up in EF Core.
I am trying to achieve the following with the code first entities and the Fluent API:
A Package can contain any number of PackageEntries
Each PackageEntry has a reference to a single Package entity (a different instance of a package, unrelated to the parent Package reference that contains the collection of PackageEntries)
The two entities:
public class Package{
public Package()
{
_packageEntries = new List<PackageEntry>();
}
//trimmed other properties
private readonly List<PackageEntry> _packageEntries;
[NotMapped]
public IReadOnlyCollection<PackageEntry> PackageEntries => _packageEntries.ToList().AsReadOnly();
}
and
public class PackageEntry
{
public int DisplayOrder { get; set; }
public int PackageID { get; set; }
public Package Package { get; set; }
public int Quantity { get; set; }
public Package ParentPackage { get; set; }
public int ParentPackageID { get; set; }
}
What I currently have using the Fluent API, which is not working is:
modelBuilder.Entity<Package>().HasMany(x => x.PackageEntries).WithOne();
modelBuilder.Entity<PackageEntry>().HasOne(x => x.Package).WithOne().HasForeignKey(typeof(PackageEntry), "PackageID");
It isn't throwing errors, but what I am seeing is that when a PackageEntry is added to a package, it is not getting saved when calling SaveChanges on the context.
Am I doing something wrong with the Fluent API or something else?
EDIT
I had missed adding the top level package to the context, once that was done the package entry that gets added to it is being saved. I would still appreciate comments on the Fluent API setup and any best practices.
From the PackageEntry entity, I need to know both the Parent Package and the contained Package which will be separate references to the same type. I can't seem to set this up with the Fluent API, when the Parent Package is loaded via EF it doesn't contain any PackageEntry objects, even if their ParentPackageID is set correctly.
Upon some offline advice from an EF expert, I have worked around this issue by removing the navigation property for PackageEntry.Package and simply manually handle the foreign key for that package entity.
Once I did that, now when the Parent Package entity is loaded, it properly loads the children PackageEntries.
So, the PackageEntry class now looks like this:
public class PackageEntry
{
public int DisplayOrder { get; set; }
public int PackageID { get; set; }
//public Package Package { get; set; } //Handle manually
public int Quantity { get; set; }
public Package ParentPackage { get; set; }
public int ParentPackageID { get; set; }
}
And the Fluent API code:
navigation = builder.Metadata.FindNavigation(nameof(Package.PackageEntries));
//EF access the PackageEntries collection property through its backing field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
modelBuilder.Entity<Package>().HasMany(x => x.PackageEntries)
.WithOne("ParentPackage")
.HasForeignKey(nameof(PackageEntry.ParentPackageID))
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
Your Package.PackageEntries collection is marked [NotMapped], and it does not have a setter. No matter what, EntityFramework is not going to pick that up.
I've never tried using an IReadonlyCollection<T> with EntityFramework, but I would imagine that EF won't like that either.
Your first try should be to remove the attribute and arrange the property like this:
public virtual IReadOnlyCollection<PackageEntry> PackageEntries {
get {
return _packageEntries.ToList().AsReadonly();
}
protected internal set {
_packageEntries = value;
}
}
Granted, that would require you to remove the readonly from the private member variable.
That being said, I'm not sure if EF has an internal list that it eventually assigns to the property, but I would imagine that it would just call the Add() method on the collection (which is why your properties must be ICollection<T> instead of IEnumerable<T>.
Therefore, if that is all still not working, you should make _packageEntries protected internal and use that as your EF collection. Then you can only publicly expose your PackageEntries as you are doing now.
I have a simple POCO type, say something like
public class OwnedEntity {
public string stringProperty { get; set; }
public decimal decimalProperty { get; set; }
public bool boolProperty { get; set; }
public int intProperty { get; set; }
}
and an actual entity with an OwnedEntity reference
public class SomeEntity {
public string Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
}
I set up the relationship like described in the documentation using EF Core's Fluent API:
protected override void OnModelCreating (ModelBuilder builder) {
base.OnModelCreating (builder);
builder.Entity<SomeEntity> ().OwnsOne (e => e.OwnedEntity);
}
I can't find anything on how to define default-values for all the properties of OwnedEntity. I tried to initialize the properties like this:
public class OwnedEntity {
public string stringProperty { get; set; } = "initial"
public decimal decimalProperty { get; set; } = -1M;
public bool boolProperty { get; set; } = false;
public int intProperty { get; set; } = -1;
}
but with no effect. Same goes with the [DefaultValueAttribute] (but that was to expect since it's explicitly mentioned).
There's a bit of information on how to handle initial values for regular entities:
modelBuilder.Entity<SomeOtherEntity>()
.Property(e => e.SomeIntProperty)
.HasDefaultValue(3);
But since I'm facing an Owned Entity Type, I can't access the type via Entity<T>.
Is there a way of doing what I'm looking for?
Some things worth mentioning:
I have a solid amount of specific entities where most of them are using the OwnsOne relation
Declaring all OwnedEntity-properties in a base class is not an option since not all the entities have those properties
I`m using EF Core 2.0.3 and ASP.NET Core MVC 2.0.4
Edit:
Originally, I wanted to have newly created SomeEntity instances to come with preset properties for all of the 'embedded' SomeEntity.OwnedEntity properties.
But looking at how my associated controller works, it all makes sense... I have the following methods for the 'Create' operation:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create (SomeEntity model) {
context.Add (model);
await context.SaveChangesAsync ();
// redirect etc.
}
Which means that no object is created for the [HttGet] overload of Create and all the HTML inputs linked to properties (via asp-for) are initially empty. Okay. So I guess the proper way of doing this is to manually create a new instance of SomeEntity and pass it to the Create view like this:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create), new SomeEntity());
}
Is this the right approach then or are there some more things to keep in mind?
Assuming you understand what EF Core Default Values are for, and just looking for equivalent of Entity<T>().Property(...) equivalent.
The owned entities are always configured for each owner type by using the ReferenceOwnershipBuilder<TEntity,TRelatedEntity> class methods. To access this class you either use the result of OwnsOne method, or use the OwnsOne overload taking second argument of type Action<ReferenceOwnershipBuilder<TEntity,TRelatedEntity>>.
For instance, using the second approach:
builder.Entity<SomeEntity>().OwnsOne(e => e.OwnedEntity, ob =>
{
ob.Property(e => e.stringProperty)
.HasDefaultValue("initial");
ob.Property(e => e.decimalProperty)
.HasDefaultValue(-1M);
// etc.
});
I would like to set up a many to many relationship in ASP.NET MVC4.
The goal is to extend the default UserProfile class with a list of Timeline objects, which belong to the user.
A Timeline could be shared by multiple users, so the Timeline class should have an list of UserProfile objects aswell.
Timeline class:
namespace MvcApplication1.Models
{
public class Timeline
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public List<UserProfile> Users { get; set; }
}
public class TimelineContext : DbContext
{
public DbSet<Timeline> Timelines { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
// Added the following because I saw it on:
// http://www.codeproject.com/Tips/548945/Generating-Many-to-Many-Relation-in-MVC4-using-Ent
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Timeline>()
.HasMany(c => c.Users)
.WithMany(s => s.Timelines)
.Map(mc =>
{
mc.ToTable("TimelineOwners");
mc.MapLeftKey("TimelineId");
mc.MapRightKey("UserId");
});
}
}
}
UserProfile class (default class with an added property):
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Timeline> Timelines { get; set; }
// Added the following because I saw it on:
// http://www.codeproject.com/Tips/548945/Generating-Many-to-Many-Relation-in-MVC4-using-Ent
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserProfile>()
.HasMany(c => c.Timelines)
.WithMany(s => s.Users)
.Map (mc =>
{
mc.ToTable("TimelineOwners");
mc.MapLeftKey("UserId");
mc.MapRightKey("TimelineId");
});
}
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public List<Timeline> Timelines { get; set; }
}
I have a connection table with foreign keys:
When creating an instance of Timeline, the Users list is null:
Timeline timeline = db.Timelines.Find(id); // timeline.Users = null
Can somebody please enlighten me, how should I set this up working?
I'm totally new to ASP.NET MVC4.
Edit 1: I understand I should not extend UserProfile but create another class to store users. Once the many-to-many relationship works, I will refactor and go into that direction.
But first I would like to know why is it not working.
Edit 2:
The double context also caused problems, two databases were created for the two contexts and the pure join table was empty in one of them.
I suggest that you work through this article about the options how you can load navigation properties with Entity Framework. This is very basic knowledge which is important for every kind of relationship, not only many-to-many relationships.
Looking at that article you will find then that this line...
Timeline timeline = db.Timelines.Find(id);
...does not load any related entities. So, it's expected that timeline.Users is null, even if the entities are related in the database.
If you want to load the Users you can use eager loading:
Timeline timeline = db.Timelines.Include(t => t.Users)
.SingleOrDefault(t => t.Id == id);
This is a single database query. Or to enable lazy loading you have to mark your navigation properties as virtual:
public virtual List<UserProfile> Users { get; set; }
//...
public virtual List<Timeline> Timelines { get; set; }
You can then use your original code:
Timeline timeline = db.Timelines.Find(id); // first query
var users = timeline.Users; // second query
This will run two separate queries. The second is performed as soon as you access the navigation property for the first time.
BTW: Is there a reason why you have two context classes - TimelineContext and UsersContext? "Normally" you need only one context.
I'm not a fan of messing with the working of the internal userprofile. I would suggest creating your own user class, linking it to the simplemembershipprovider and adding functionality there. At max you'll extend the accountclasses a little to add more fields to register with, but that's about it.
Follow this extremely handy guide to get things working and let me know if you encounter an error.
I am currently working on a Web API project with a Database-First method using Entity Framework (which I know is not the most stable of platforms yet), but I am running into something very strange.
When the GET method within my APIController tries to return all records in a DbSet with a LINQ Include() method involved such as this, it will return a 500 error:
// GET api/Casinos
public IEnumerable<casino> Getcasinos()
{
var casinos = db.casinos.Include(c => c.city).Include(c => c.state);
return casinos.AsEnumerable();
}
Yet, this method works fine, and returns my data from within my database:
// GET api/States
public IEnumerable<state> Getstates()
{
return db.states.AsEnumerable();
}
So I have proved in other instances that if it returns the entities without LINQ queries, it works great, yet when there is an Include method used upon the DbContext, it fails.
Of course, trying to find this error is impossible, even with Fiddler, Chrome/Firefox dev tools, and adding in GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
If anyone has resolved this, it would be nice to know a nice resolution so I can start returning my data! Thanks!:)
P.S. I am using SQL Server 2012
This is happening due to error in serialization (Json/XML). The problem is you are directly trying to transmit your Models over the wire. As an example, see this:
public class Casino
{
public int ID { get; set; }
public string Name { get; set; }
public virtual City City { get; set; }
}
public class State
{
public int ID { get; set; }
public string Name { get; set; }
[XmlIgnore]
[IgnoreDataMember]
public virtual ICollection<City> Cities { get; set; }
}
public class City
{
public int ID { get; set; }
public string Name { get; set; }
public virtual State State { get; set; }
[XmlIgnore]
[IgnoreDataMember]
public virtual ICollection<Casino> Casinos { get; set; }
}
public class Context : DbContext
{
public Context()
: base("Casino")
{
}
public DbSet<Casino> Casinos { get; set; }
public DbSet<State> States { get; set; }
public DbSet<City> Cities { get; set; }
}
Pay attention to the XmlIgnore and IgnoreDataMember. You need to restrict avoiding serialization so it doesn't happen in circular manner. Further, the above model will still not work because it has virtual. Remove virtual from everywhere namely City, Cities, Casinos and State and then it would work but that would be inefficient.
To sum up: Use DTOs and only send data that you really want to send instead of directly sending your models.
Hope this helps!
I had same problem in ASP.Net Core Web Api and made it working with this solution:
Add Microsoft.AspNetCore.Mvc.NewtonsoftJson nuget package to web api project.
and in Startup.cs class in ConfigureServices method add this code:
services.AddControllersWithViews().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);