I have a problem with the realization of data hierarchy in EF Core 3.0 (I need to use TPH).
Let's look:
public abstract class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public abstract UserProfile **Profile** { get; set; }
}
public class ConcreteUser1 : User
{
public string ConcreteUser1Prop { get; set; }
public override UserProfile Profile { get; set; }
}
public class ConcreteUser2 : User
{
public string ConcreteUser2Prop { get; set; }
public override UserProfile Profile { get; set; }
}
And here we have Profile classes:
public class UserProfile
{
public Guid Id { get; set; }
public string SameProp { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
}
public class ConcreteUser1Profile : Profile
{
public string ConcreteProfile1Prop { get; set; }
}
public class ConcreteUser2Profile : Profile
{
public string ConcreteProfile2Prop { get; set; }
}
Let it be only one DbSet<User> which can be used for getting data from Database.
So, I can't understand, how to say EF Core (3.0) 2 things: how to say to store ConcreteUser*Profile (possible, Add and SaveChanges will work properly and write concrete profile type when I add concrete type user with concrete profile type. But I don't know how to say EF Core to get the correct concrete profile type when I use Where or FirstOrDefault method?
Is this model correct in principle?
Upd.
For example, I have 2 records in my Db: ConcreteUser1 user1, ConcreteUser2 user2. So, let's have DbSet<User> Users, so, what will I have after the request: var tstUser = ExampleDbContext.Users.FirstOrDefault();? What will the type of Profile variable in tstUser.Profile?
You can use HasDiscriminator on model creating.
It will use a field to be able to decide which concrete user class you're trying map the data to.
For you it'll be something like:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasDiscriminator<int>("UserType")
.HasValue<ConcreteUser1>(1)
.HasValue<ConcreteUser2>(2);
}
More at:
https://www.learnentityframeworkcore.com/configuration/fluent-api/hasdiscriminator-method
I have a solution (maybe someone will use it too).
public class UserProfile
{
public Guid Id { get; set; }
public ProfileRoles Role { get; set;}
public string SameProp { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
}
public class ConcreteUser1Profile : UserProfile
{
public string ConcreteProfile1Prop { get; set; }
}
public class ConcreteUser2Profile : UserProfile
{
public string ConcreteProfile2Prop { get; set; }
}
and
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public UserRoles Role { get; set; }
public UserProfile Profile { get; set; }
}
public class ConcreteUser1 : User
{
public string ConcreteUser1Prop { get; set; }
}
public class ConcreteUser2 : User
{
public string ConcreteUser2Prop { get; set; }
}
public enum UserRoles
{
User = 0,
ConcreteUser1 = 1,
ConcreteUser2 = 2
}
public enum ProfileRoles
{
BaseProfile = 0,
ConcreteProfile1 = 1,
ConcreteProfile2 = 2
}
And It needs to be configured well in the DbContext:
public class UserStoreDbContext : DbContext
{
DbSet<User> Users { get; set; }
DbSet<UserProfile> Profiles { get; set; }
public UserStoreDbContext(DbContextOptions<UserStoreDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>(builder =>
{
builder.HasDiscriminator<UserRoles>(x => x.Role)
.HasValue<User>(Roles.User)
.HasValue<Logistian>(Roles.ConcreteUser1)
.HasValue<Driver>(Roles.ConcreteUser2)
});
modelBuilder.Entity<UserProfile>(builder =>
{
builder.HasDiscriminator<ProfileRoles>(x => x.Role)
.HasValue<UserProfile>(ProfileRoles.BaseProfile)
.HasValue<ConcreteUser1Profile>(Roles.ConcreteProfile1)
.HasValue<ConcreteUser2Profile>(Roles.ConcreteProfile2)
});
}
}
In this case when you will use the code like: dbContext.Users.FirstOrDefault(), you also will get correct derived profile from UserProfile.
Maybe someone will need this solution too.
Related
I have a Repository base class like so:
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
{
protected RepositoryContext RepositoryContext;
public RepositoryBase(RepositoryContext repositoryContext)
=> RepositoryContext = repositoryContext;
public IQueryable<T> FindByCondition(Expression<Func<T, bool>>
expression, bool trackChanges)
{
return !trackChanges ?
RepositoryContext.Set<T>().Where(expression).AsNoTracking() :
RepositoryContext.Set<T>().Where(expression);
}
}
This is working fine to retrieve a single entity. But how can I modify it to retrieve its navigational properties too?
I saw this kind of answer here on Stackoverflow. But I do not know how to apply it to my base class above. Any help, please.
RepositoryContext.Model.GetEntityTypes()
.Select(t => new
{
t.ClrType.Name,
NavigationProperties = t.GetNavigations()
.Select(x => x.PropertyInfo)
});
My query is like so:
public async Task<IEnumerable<Fare>>
GetAllFaresByUserAsync(string userId, bool trackChanges)
{
return await FindByCondition(e => e.UserId.Equals(userId),
trackChanges).ToListAsync();
}
The models are like so: I can get the Fares collection using my query above. But "extras": [], is the problem here?
Fare
public class Fare
{
[Column("FareId")]
public Guid Id { get; set; }
public decimal? Amount { get; set; }
public int? Date { get; set; }
public decimal? FareTips { get; set; }
public ICollection<Extra>? Extras { get; set; }
[ForeignKey(nameof(User))]
public string? UserId { get; set; }
public User? User { get; set; }
}
Extra
public class Extra
{
[Column("ExtraId")]
public Guid Id { get; set; }
[Required(ErrorMessage = "Name is a required field.")]
public string? Name { get; set; }
[Required(ErrorMessage = "Amount is a required field.")]
public decimal? Amount { get; set; }
[ForeignKey(nameof(Fare))]
public Guid FareId { get; set; }
public Fare? Fare { get; set; }
[ForeignKey(nameof(User))]
public string? UserId { get; set; }
public User? User { get; set; }
}
You should have a look at how to configure the model to auto-include navigation properties. There's an AutoInclude method on the model builder that does this for you automatically.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Fare>().Navigation(f => f.Extras).AutoInclude();
}
I am using .Net Core 3. Brand new project, with Identity.
I've made a simple new entity to add to the database. I get the following error on startup. As you can see, I have already added the [Key] attribute to the entity and it will still not register.
public class DataContext : IdentityDbContext
{
public DataContext(DbContextOptions<DataContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
public DbSet<LocationRecord> LocationRecords { get; set; }
public DbSet<LocationActivity> LocationActivities { get; set; }
}
And the model class:
public class LocationActivity
{
[Key]
public Guid Id;
public DateTime Moment { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public int Confidence { get; set; }
public string ActivityType { get; set; }
public string UserId { get; set; }
[ForeignKey(nameof(UserId))]
public IdentityUser User { get; set; }
}
Your Id is a field rather than a property. Try rewriting like so:
public class LocationActivity {
[Key]
public Guid Id {get; set;}
.......
Working with .NET Core 3.0 and EF Core if that impacts things.
I'm trying to define a model where:
A user owns a collection of toys.
A user can have a currently selected toy.
I'm trying to model this as such (BaseModel has common properties to all my entities), and AVUser is my ASP.NET Identity user class.
public abstract class BaseModel
{
public int Id { get; set; }
public DateTime LastModifiedDate { get; set; }
public DateTime CreatedDate { get; set; }
public AVUser CreatedUser { get; set; }
public AVUser ModifiedUser { get; set; }
}
public class Toy: BaseModel
{
[MaxLength(80)]
public string Name { get; set; }
}
public class AVUser : IdentityUser
{
public string FirstName { get; set; }
// The currently selected toy for the user.
public int SelectedToyId { get; set; }
public Toy SelectedToy { get; set; }
}
However, EF Core throws an error stating: Unable to determine the relationship represented by navigation property 'AVUser.Toy' of type 'Toy'.
I am having trouble how I annotate this so it knows that the user can have a collection of toys, and I want to store a single toy with the user as the currently selected one.
Write your model classes as follows:
public class Toy: BaseModel
{
[MaxLength(80)]
public string Name { get; set; }
[ForeignKey("AVUser")]
public string UserId { get; set; }
public AVUser AVUser { get; set; }
}
public class AVUser : IdentityUser
{
public string FirstName { get; set; }
// The currently selected toy for the user.
[ForeignKey("SelectedToy")]
public int SelectedToyId { get; set; }
public Toy SelectedToy { get; set; }
public ICollection<Toy> Toys {get; set;}
}
Then configure in the OnModelCreating in DbContext as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<AVUser>().HasOne(a => a.SelectedToy).WithOne();
modelBuilder.Entity<AVUser>().HasMany(a => a.Toys).WithOne(t => t.AVUser).HasForeignKey(t => t.UserId);
}
Here's the problem. I have table User which have quite a few fields. What I want to do is split this table into multiple entities like this:
User
-> GeneralDetails
-> CommunicationDetails
-> Address
etc.
All goes well when extracting some fields from User into GeneralDetails. However, when I try to do the same thing for CommunicationDetails EF blows up and require to establish one-to-one relationship between GeneralDetails and CommunicationDetails.
Sample entities definition:
public class User {
public int UserId { get; set; }
public string SomeField1 { get; set; }
public int SomeField2 { get; set; }
public virtual GeneralDetails GeneralDetails { get; set; }
public virtual CommunicationDetails CommunicationDetails { get; set; }
public virtual Address Address { get; set; }
}
public class GeneralDetails {
[Key]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public virtual User User { get;set; }
}
public class CommunicationDetails {
[Key]
public int UserId { get; set; }
public string Phone { get; set; }
public string DeviceToken { get; set; }
public virtual User User { get;set; }
}
public class Address {
[Key]
public int UserId { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string Street { get; set; }
public virtual User User { get;set; }
}
Sample mapping:
modelBuilder.Entity<User>().
HasRequired(user => user.GeneralDetails).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().
HasRequired(user => user.CommunicationDetails).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().
HasRequired(user => user.Address).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<GeneralDetails>().ToTable("Users");
modelBuilder.Entity<Address>().ToTable("Users");
Why on earth EF want this relationship? Is there any way this could be solved?
The correct way to actually do this is by Complex Types rather than entities. Its actually a more common problem than you think.
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelbuilder.ComplexType<CommunicationDetails>();
modelbuilder.ComplexType<GeneralDetails>();
modelbuilder.ComplexType<Address>();
modelbuilder.Entity<User>().ToTable("Users");
}
}
I have an Entity in Code First Entity framework that currently looks like this:
public class Entity
{
// snip ...
public string OriginalDepartment { get; set; }
public string OriginalQueue { get; set; }
public string CurrentDepartment { get; set; }
public string CurrentQueue { get; set; }
}
I would like to create Complex Type for these types as something like this:
public class Location
{
public string Department { get; set; }
public string Queue { get; set; }
}
I'd like to use this same type for both Current and Original:
public Location Original { get; set; }
public Location Current { get; set; }
Is this possible, or do I need to create two complex types CurrentLocation and OriginalLocation?
public class OriginalLocation
{
public string Department { get; set; }
public string Queue { get; set; }
}
public class CurrentLocation
{
public string Department { get; set; }
public string Queue { get; set; }
}
It is supported out of box, you do not need to create two complex types.
You can also configure your complex types explicitely with model builder
modelBuilder.ComplexType<Location>();
To customize column names, you should configure them from parent entity configuration
public class Location
{
public string Department { get; set; }
public string Queue { get; set; }
}
public class MyEntity
{
public int Id { get; set; }
public Location Original { get; set; }
public Location Current { get; set; }
}
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<Location>();
modelBuilder.Entity<MyEntity>().Property(x => x.Current.Queue).HasColumnName("myCustomColumnName");
}
}
This will map MyEntity.Current.Queue to myCustomName column