Entity Framework: Cannot be configured on 'xxx' class because it is a derived type - entity-framework

I got this error when try add "AppUserRoles" table by myself to access UserRoles from User, for example:
from u in _userManager.Users.Where(x => x.UserRoles "contain" "some codition here")
And I got this error:
A key cannot be configured on 'AppUserRoles' because it is a derived type. The key must be configured on the root type 'IdentityUserRole'. If you did not intend for 'IdentityUserRole' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.
This is my previous code, It is run ok:
builder.Entity<IdentityUserRole<Guid>>().ToTable("AppUserRoles")
.HasKey(x => new { x.RoleId, x.UserId });
--->And I change to this:
My user class
[Table("AppUsers")]
public class AppUser : IdentityUser<Guid>, IDateTracking, ISwitchable
{
public virtual ICollection<AppUserRoles> UserRoles { get; set; }
}
My role class
[Table("AppRoles")]
public class AppRole : IdentityRole<Guid>
{
public virtual ICollection<AppUserRoles> UserRoles { get; set; }
}
My appUserRole class:
[Table("AppUserRoles")]
public class AppUserRoles : IdentityUserRole<Guid>
{
public virtual AppUser User { get; set; }
public virtual AppRole Role { get; set; }
}
My DbContextClass
public class AppDbContext : IdentityDbContext<AppUser, AppRole, Guid>
{
public AppDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<AppUser> AppUsers { get; set; }
public DbSet<AppRole> AppRoles { get; set; }
public DbSet<AppUserRoles> AppUserRoles { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
#region Identity Config
builder.Entity<IdentityUserClaim<Guid>>().ToTable("AppUserClaims").HasKey(x => x.Id);
builder.Entity<IdentityUserLogin<Guid>>().ToTable("AppUserLogins").HasKey(x => x.UserId);
builder.Entity<AppUserRoles>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
builder.Entity<IdentityUserToken<Guid>>().ToTable("AppUserTokens")
.HasKey(x => new { x.UserId });
#endregion Identity Config
}
public override int SaveChanges()
{
var modified = ChangeTracker.Entries().Where(e => e.State == EntityState.Modified || e.State == EntityState.Added);
foreach (EntityEntry item in modified)
{
var changedOrAddedItem = item.Entity as IDateTracking;
if (changedOrAddedItem != null)
{
if (item.State == EntityState.Added)
{
changedOrAddedItem.DateCreated = DateTime.Now;
}
changedOrAddedItem.DateModified = DateTime.Now;
}
}
return base.SaveChanges();
}
}
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args)
{
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json").Build();
var builder = new DbContextOptionsBuilder<AppDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
return new AppDbContext(builder.Options);
}
}
This is my startup file:
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
o => o.MigrationsAssembly("YayoiApp.Data.EF")),
ServiceLifetime.Scoped);
Please give me some advise, I already have researched to much, but not found a solution. Thanks.

For custom IdentityUserRole<Guid>, you need to change your DbContext like
public class ApplicationDbContext : IdentityDbContext<AppUser
, AppRole
, Guid
, IdentityUserClaim<Guid>
, AppUserRoles
, IdentityUserLogin<Guid>
, IdentityRoleClaim<Guid>
, IdentityUserToken<Guid>>
{
Then register it in Startup.cs
services.AddIdentity<AppUser, AppRole>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
UseCase:
public class HomeController : Controller
{
private readonly UserManager<AppUser> _userManager;
private readonly RoleManager<AppRole> _roleManager;
public HomeController(UserManager<AppUser> userManager
, RoleManager<AppRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
public async Task<IActionResult> Index()
{
var userName = "Tom";
var passWord = "1qaz#WSX";
var appUser = new AppUser
{
UserName = userName
};
await _userManager.CreateAsync(appUser, passWord);
var roleName = "Admin";
await _roleManager.CreateAsync(new AppRole { Name = roleName });
await _userManager.AddToRoleAsync(appUser, roleName);
var roles = appUser.UserRoles;
return View();
}

Related

How do you find or know where the "non-owned" entity type when trying to create a migration?

I have the following classes:
JobSeeker which owns a CreditCard which has a CreditCardType
public class JobSeeker : Entity
{
private readonly List<CreditCard> _creditCards;
public IEnumerable<CreditCard> CreditCards => _creditCards.AsReadOnly();
}
public class CreditCard : Entity
{
public CreditCardType CardType { get { return CreditCardType.From(_creditCardTypeID); } private set { } }
private readonly int _creditCardTypeID;}
public class CreditCardType : Enumeration
{
public static readonly CreditCardType Amex = new CreditCardType(1, nameof(Amex).ToLowerInvariant());
public static readonly CreditCardType Visa = new CreditCardType(2, nameof(Visa).ToLowerInvariant());
public static readonly CreditCardType MasterCard = new CreditCardType(3, nameof(MasterCard).ToLowerInvariant());
public static IEnumerable<CreditCardType> List() => new[] { Amex, Visa, MasterCard };}
My DBContext Configs are:
class JobSeekerEntityTypeConfiguration : IEntityTypeConfiguration<JobSeeker>
{
public void Configure(EntityTypeBuilder<JobSeeker> jsConfiguration)
{
if (jsConfiguration == null)
{
throw new ArgumentNullException(nameof(jsConfiguration));
}
// Build the model
jsConfiguration.OwnsOne(s => s.CompleteName);
jsConfiguration.OwnsOne(s => s.HomeAddress);
jsConfiguration.OwnsOne(s => s.BillingAddress);
jsConfiguration.OwnsOne(s => s.EmAddress);
jsConfiguration.OwnsOne(s => s.PersonalPhoneNumber);
jsConfiguration.OwnsMany(a => a.CreditCards);
//jsConfiguration.HasMany<CreditCard>().WithOne(JobSeeker).OnDelete(DeleteBehavior.Restrict);
jsConfiguration.Property<DateTime>("CreatedDate");
jsConfiguration.Property<DateTime>("UpdatedDate");
}
}
class CreditCardTypeEntityTypeConfiguration : IEntityTypeConfiguration<CreditCard>
{
public void Configure(EntityTypeBuilder<CreditCard> ccConfiguration)
{
if (ccConfiguration == null)
{
throw new ArgumentNullException(nameof(ccConfiguration));
}
// Build the model
ccConfiguration.HasOne(o => o.CardType).WithMany().HasForeignKey("_creditCardTypeID");
ccConfiguration.Property<DateTime>("CreatedDate");
ccConfiguration.Property<DateTime>("UpdatedDate");
}
}
class CreditCardEntityTypeConfiguration : IEntityTypeConfiguration<CreditCardType>
{
public void Configure(EntityTypeBuilder<CreditCardType> cctConfiguration)
{
if (cctConfiguration == null)
{
throw new ArgumentNullException(nameof(cctConfiguration));
}
// Build the model
cctConfiguration.ToTable("CreditCardTypes");
cctConfiguration.HasKey(o => o.Id);
cctConfiguration.Property(o => o.Id)
.HasDefaultValue(1)
.ValueGeneratedNever()
.IsRequired();
cctConfiguration.Property(o => o.Name)
.HasMaxLength(200)
.IsRequired();
cctConfiguration.HasData(
new { Id = 1, Name = "Amex" },
new { Id = 2, Name = "Visa" },
new { Id = 3, Name = "MasterCard" });
}
}
My DB Context is:
public class JobSeekerContext : DbContext, IUnitOfWork
{
private static readonly Type[] EnumerationTypes = { typeof(CreditCardType) };
public const string DEFAULT_SCHEMA = "jobseeker";
private readonly ILoggerFactory MyConsoleLoggerFactory;
private readonly IMediator Mediator;
public DbSet<JobSeeker> JobSeekers { get; set; }
public DbSet<CreditCard> CreditCards { get; set; }
public DbSet<CreditCardType> CreditCardTypes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (modelBuilder == null)
{
throw new ArgumentNullException(nameof(modelBuilder));
}
// Build the model
modelBuilder.ApplyConfiguration(new CreditCardTypeEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new CreditCardEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new JobSeekerEntityTypeConfiguration());
}
When I run the migration I get the following error: "The type 'CreditCard' cannot be marked as owned because a non-owned entity type with the same name already exists."
Where is CreditCard marked as non-owned?
Where is CreditCard marked as non-owned?
In JobSeekerContext here
public DbSet<CreditCard> CreditCards { get; set; }
and here
modelBuilder.ApplyConfiguration(new CreditCardTypeEntityTypeConfiguration());
and the whole (misleadingly named) CreditCardTypeEntityTypeConfiguration class since it is IEntityTypeConfiguration<CreditCard>.
Owned entity types are special entities which are configured, queried and updated only through owner.
Here is the excerpt from the By-design restrictions section of the current EF Core documentation:
You cannot create a DbSet<T> for an owned type.
You cannot call Entity<T>() with an owned type on ModelBuilder.
Note that applying IEnityTypeConfiguration<T> class is equivalent of calling Entity<T>() on ModelBuilder.
So you are breaking both aforementioned restrictions.
What you need to do is
Remove the DbSet<CreditCard> property from the context
Remove CreditCardTypeEntityTypeConfiguration class and corresponding ApplyConfiguration call
Move the CreditCard configuration code inside the owner JobSeeker configuration using the builder provided/returned by the OwnsMany method. e.g.
jsConfiguration.OwnsMany(a => a.CreditCards, ccConfiguration =>
{
// Build the model
ccConfiguration.HasOne(o => o.CardType).WithMany().HasForeignKey("_creditCardTypeID");
ccConfiguration.Property<DateTime>("CreatedDate");
ccConfiguration.Property<DateTime>("UpdatedDate");
});

EF Core 3.1: Navigation property doesn't lazy load entities when calling the backing field first

I am using EF Core 3.1.7. The DbContext has the UseLazyLoadingProxies set. Fluent API mappings are being used to map entities to the database. I have an entity with a navigation property that uses a backing field. Loads and saves to the database seem to work fine except for an issue when accessing the backing field before I access the navigation property.
It seems that referenced entities don't lazy load when accessing the backing field. Is this a deficiency of the Castle.Proxy class or an incorrect configuration?
Compare the Student class implementation of IsRegisteredForACourse to the IsRegisteredForACourse2 for the behavior in question.
Database tables and relationships.
Student Entity
using System.Collections.Generic;
namespace EFCoreMappingTests
{
public class Student
{
public int Id { get; }
public string Name { get; }
private readonly List<Course> _courses;
public virtual IReadOnlyList<Course> Courses => _courses.AsReadOnly();
protected Student()
{
_courses = new List<Course>();
}
public Student(string name) : this()
{
Name = name;
}
public bool IsRegisteredForACourse()
{
return _courses.Count > 0;
}
public bool IsRegisteredForACourse2()
{
//Note the use of the property compare to the previous method using the backing field.
return Courses.Count > 0;
}
public void AddCourse(Course course)
{
_courses.Add(course);
}
}
}
Course Entity
namespace EFCoreMappingTests
{
public class Course
{
public int Id { get; }
public string Name { get; }
public virtual Student Student { get; }
protected Course()
{
}
public Course(string name) : this()
{
Name = name;
}
}
}
DbContext
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreMappingTests
{
public sealed class Context : DbContext
{
private readonly string _connectionString;
private readonly bool _useConsoleLogger;
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public Context(string connectionString, bool useConsoleLogger)
{
_connectionString = connectionString;
_useConsoleLogger = useConsoleLogger;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information)
.AddConsole();
});
optionsBuilder
.UseSqlServer(_connectionString)
.UseLazyLoadingProxies();
if (_useConsoleLogger)
{
optionsBuilder
.UseLoggerFactory(loggerFactory)
.EnableSensitiveDataLogging();
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>(x =>
{
x.ToTable("Student").HasKey(k => k.Id);
x.Property(p => p.Id).HasColumnName("Id");
x.Property(p => p.Name).HasColumnName("Name");
x.HasMany(p => p.Courses)
.WithOne(p => p.Student)
.OnDelete(DeleteBehavior.Cascade)
.Metadata.PrincipalToDependent.SetPropertyAccessMode(PropertyAccessMode.Field);
});
modelBuilder.Entity<Course>(x =>
{
x.ToTable("Course").HasKey(k => k.Id);
x.Property(p => p.Id).HasColumnName("Id");
x.Property(p => p.Name).HasColumnName("Name");
x.HasOne(p => p.Student).WithMany(p => p.Courses);
});
}
}
}
Test program which demos the issue.
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using System.Linq;
namespace EFCoreMappingTests
{
class Program
{
static void Main(string[] args)
{
string connectionString = GetConnectionString();
using var context = new Context(connectionString, true);
var student2 = context.Students.FirstOrDefault(q => q.Id == 5);
Console.WriteLine(student2.IsRegisteredForACourse());
Console.WriteLine(student2.IsRegisteredForACourse2()); // The method uses the property which forces the lazy loading of the entities
Console.WriteLine(student2.IsRegisteredForACourse());
}
private static string GetConnectionString()
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
return configuration["ConnectionString"];
}
}
}
Console Output
False
True
True
When you declare a mapped property in an EF entity as virtual, EF generates a proxy which is capable of intercepting requests and assessing whether the data needs to be loaded. If you attempt to use a backing field before that virtual property is accessed, EF has no "signal" to lazy load the property.
As a general rule with entities you should always use the properties and avoid using/accessing backing fields. Auto-initialization can help:
public virtual IReadOnlyList<Course> Courses => new List<Course>().AsReadOnly();

EF Core: Only part of the model is saved to the database

I try to use EF core, but only a part of my model is saved to the database.
This is my model:
public class EngineType
{
public string Name { get; set; }
}
public class Car
{
public long CarId { get; set; }
public string Name { get; set; }
public EngineType Engine { get; set; }
}
The CarId and the Name is saved, but not the EngineType.
This is the test I use, but actual.Engine is always null:
[TestMethod]
public void WhenIAddAndSaveANewCarThenItIsAddedToDB()
{
using var target = new EFCoreExampleContext();
using var concurrentContext = new EFCoreExampleContext();
var expected = new Car() {CarId = 0815, Name = "Isetta", Engine = new EngineType() { Name = "2Takt" }};
target.Cars.Add(expected);
target.SaveChanges();
var actual = concurrentContext.Cars.Single();
Assert.AreEqual(1, concurrentContext.Cars.Count());
Assert.IsNotNull(actual.Engine);
Assert.AreEqual(expected, actual);
}
My Context looks like this:
public class EFCoreExampleContext : DbContext
{
public DbSet<Car> Cars { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase(databaseName: "Add_writes_to_database");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EngineType>(
d =>
{
d.HasKey(e => e.Name);
d.Property(e => e.Name).IsRequired();
});
modelBuilder.Entity<EngineType>(
d =>
{
d.HasKey(e => e.Name);
});
modelBuilder.Entity<Car>(
d =>
{
d.HasKey(e => e.CarId);
d.Property<DateTime>("LastChanged").IsRowVersion().ValueGeneratedOnAddOrUpdate();
d.Property<string>("EngineForeignKey");
d.HasOne(e => e.Engine)
.WithMany()
.HasForeignKey("EngineForeignKey")
.IsRequired();
});
}
}
Any idea what am I doing wrong (or which existing topic answers this question - I even didn't have the right search words to find it).
Thanks!
I think there is no issue with saving. Entity Framework does not do eager loading by default. So you have to explicitly include any navigational properties that should be in result. Try this when you are fetching actual,
using Microsoft.EntityFrameworkCore;
var actual = concurrentContext.Cars.Include(c => c.Engine).Single();

EfCore 3 and Owned Type in same table, How do you set owned instance

How do you set owned type instance with efcore3?
In following example an exception is raised
'The entity of type 'Owned' is sharing the table 'Principals' with
entities of type 'Principal', but there is no entity of this type with
the same key value that has been marked as 'Added'.
If I set Child property inline savechanges doesn't update child properties
I can't find any example about this. I tried with several efcore3 builds and daily builds. What didn't I understand?
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace TestEF
{
class Program
{
static void Main(string[] args)
{
var id = Guid.NewGuid();
using (var db = new Ctx())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var p = new Principal {Id = id};
db.Principals.Add(p);
db.SaveChanges();
}
using (var db = new Ctx())
{
var p = db.Principals.Single(o => o.Id == id);
p.Child = new Owned();
p.Child.Prop1 = "Test2";
p.Child.Prop2 = "Test2";
db.SaveChanges();
}
}
public class Principal
{
public Guid Id { get; set; }
public Owned Child { get; set; }
}
public class Owned
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
public class Ctx : DbContext
{
public DbSet<Principal> Principals { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=TestEF;Trusted_Connection=True;Persist Security Info=true");
}
protected override void OnModelCreating(ModelBuilder mb)
{
var emb = mb.Entity<Principal>();
emb
.OwnsOne(o => o.Child, cfg =>
{
cfg.Property(o => o.Prop1).HasMaxLength(30);
//cfg.WithOwner();
});
}
}
}
}
This is a bug, filed at https://github.com/aspnet/EntityFrameworkCore/issues/17422
As a workaround you could make the child appear as modified:
db.ChangeTracker.DetectChanges();
var childEntry = db.Entry(p.Child);
childEntry.State = EntityState.Modified;
db.SaveChanges();
Try this instead:
_context.Update(entity);
This will update all the owned properties so SaveChanges() updates them, too.

Best Practices to localize entities with EF Code first

I am developing a domain model using EF Code First to persist the data. I have to add support for multilanguage and I would like not to contaminate the domain model with location concepts.
I like that in database exists a ProductTranslate table with title and Language fields but in my domain title belongs to the Product entity.
Someone knows how to get this?
Here is what I use and works well with code first.
Define a base Translation class:
using System;
public abstract class Translation<T> where T : Translation<T>, new()
{
public Guid Id { get; set; }
public string CultureName { get; set; }
protected Translation()
{
Id = Guid.NewGuid();
}
}
Define a TranslationCollection class:
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
public class TranslationCollection<T> : Collection<T> where T : Translation<T>, new()
{
public T this[CultureInfo culture]
{
get
{
var translation = this.FirstOrDefault(x => x.CultureName == culture.Name);
if (translation == null)
{
translation = new T();
translation.CultureName = culture.Name;
Add(translation);
}
return translation;
}
set
{
var translation = this.FirstOrDefault(x => x.CultureName == culture.Name);
if (translation != null)
{
Remove(translation);
}
value.CultureName = culture.Name;
Add(value);
}
}
public T this[string culture]
{
get
{
var translation = this.FirstOrDefault(x => x.CultureName == culture);
if (translation == null)
{
translation = new T();
translation.CultureName = culture;
Add(translation);
}
return translation;
}
set
{
var translation = this.FirstOrDefault(x => x.CultureName == culture);
if (translation != null)
{
Remove(translation);
}
value.CultureName = culture;
Add(value);
}
}
public bool HasCulture(string culture)
{
return this.Any(x => x.CultureName == culture);
}
public bool HasCulture(CultureInfo culture)
{
return this.Any(x => x.CultureName == culture.Name);
}
}
You can then use those classes in your entities, e.g.:
using System;
using System.Globalization;
public class HelpTopic
{
public Guid Id { get; set; }
public string Name { get; set; }
public TranslationCollection<HelpTopicTranslation> Translations { get; set; }
public string Content
{
get { return Translations[CultureInfo.CurrentCulture].Content; }
set { Translations[CultureInfo.CurrentCulture].Content = value; }
}
public HelpTopic()
{
Id = Guid.NewGuid();
Translations = new TranslationCollection<HelpTopicTranslation>();
}
}
With HelpTopicTranslation defined as:
using System;
public class HelpTopicTranslation : Translation<HelpTopicTranslation>
{
public Guid Id { get; set; }
public Guid HelpTopicId { get; set; }
public string Content { get; set; }
public HelpTopicTranslation()
{
Id = Guid.NewGuid();
}
}
Now, for the code first specific side of things, use the following configuration:
using System.Data.Entity.ModelConfiguration;
using Model;
internal class HelpTopicConfiguration : EntityTypeConfiguration<HelpTopic>
{
public HelpTopicConfiguration()
{
Ignore(x => x.Content); // Ignore HelpTopic.Content since it's a 'computed' field.
HasMany(x => x.Translations).WithRequired().HasForeignKey(x => x.HelpTopicId);
}
}
And add it to your context configurations:
public class TestContext : DbContext
{
public DbSet<HelpTopic> HelpTopics { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new HelpTopicConfiguration());
}
}
When all of this is done, the following migration is generated:
using System.Data.Entity.Migrations;
public partial class AddHelpTopicTable : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.HelpTopics",
c => new
{
Id = c.Guid(false),
Name = c.String(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.HelpTopicTranslations",
c => new
{
Id = c.Guid(false),
HelpTopicId = c.Guid(false),
Content = c.String(),
CultureName = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.HelpTopics", t => t.HelpTopicId, true)
.Index(t => t.HelpTopicId);
}
public override void Down()
{
DropForeignKey("dbo.HelpTopicTranslations", "HelpTopicId", "dbo.HelpTopics");
DropIndex("dbo.HelpTopicTranslations", new[] { "HelpTopicId" });
DropTable("dbo.HelpTopicTranslations");
DropTable("dbo.HelpTopics");
}
}
Any comments and/or improvements are welcome...