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();
}
Related
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.
Whether it is possible so: it is a Messenger where the entity User content ICollection User that are collection Friends consist from the same Users?
If that possible please tell me how create a correct relationship between them in the DbContext file?
Or how better build this relationship. May be create separate entity?
Thanks in advance!
namespace Tinkl.Data.Core.Domain
{
public class User
{
public User()
{
Contacts = new List<User>();
Conversations = new List<Conversation>();
Invites = new List<User>();
}
public int UserId { get; set; }
public string NickName { get; set; }
public string EMail { get; set; }
public string Password { get; set; }
public DateTime? CreationDate { get; set; }
public DateTime? ExitDate { get; set; }
public string Picture { get; set; }
public string Status { get; set; }
public virtual ICollection<User> Invites { get; set; }
public virtual ICollection<User> Contacts { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; }
}
}
You are going in right direction, see my below code same type of self-relationship in EF code first
public class ContentEntityRef : BaseModel
{
public ContentEntityRef()
{
RoleRefs = new HashSet<RoleRef>();
}
public int EntityId { get; set; }
public string EntityName { get; set; }
public int? ParentEntityId { get; set; }
public virtual ICollection<RoleRef> RoleRefs { get; set; }
public virtual ContentEntityRef Parent { get; set; }
public virtual ICollection<ContentEntityRef> Children { get; set; }
}
I had created seprate configuration file, you can same use in dbContext "OnModelCreating" method.
internal class ContentEntityRefConfiguration : EntityTypeConfiguration<ContentEntityRef>, IEntityConfiguration
{
public ContentEntityRefConfiguration()
{
this.HasKey(x => x.EntityId).Property(t => t.EntityId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.EntityName).IsRequired().HasMaxLength(50);
this.HasMany(c => c.Children).WithOptional(c => c.Parent).HasForeignKey(c => c.ParentEntityId);
this.HasMany<RoleRef>(role => role.RoleRefs)
.WithMany(content => content.ContentEntities)
.Map(contentRole =>
{
contentRole.MapLeftKey("EntityID");
contentRole.MapRightKey("RoleID");
contentRole.ToTable("RoleEntityMap");
});
}
}
hope this will help you :)
I have the following entities:
public abstract class Meter
{
public int MeterId { get; set; }
public string EANNumber { get; set; }
public string MeterNumber { get; set; }
[Required]
public virtual Premise Premise { get; set; }
public abstract void AddReading(CounterReading reading);
}
public class GasMeter : Meter
{
public virtual Counter Counter { get; private set; }
public override void AddReading(CounterReading reading)
{
Counter.Readings.Add(reading);
}
}
public class Premise
{
[Key]
public int PremiseId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
[Required]
public virtual Address Address { get; set; }
public string Type { get; set; }
public virtual GasMeter GasMeter { get; set; }
}
I have a 1:1 relation between a GasMeter and a Premise.
What must I do so that I can set myPremise.GasMeter = myMeter, and retrieve myPremise in later code with myMeter.Premise?
Edit
When setting it up via the Fluent API as follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Premise>().HasOptional(p => p.GasMeter)
.WithRequired(m => m.Premise);
}
I get the following exception when running:
An exception of type 'System.Data.Entity.ModelConfiguration.ModelValidationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: One or more validation errors were detected during model generation:
Premise: FromRole: NavigationProperty 'Premise' is not valid. Type 'GasMeter' of FromRole 'Premise_GasMeter_Target' in AssociationType 'Premise_GasMeter' must exactly match with the type 'Meter' on which this NavigationProperty is declared on.
Does this mean that I can't use Navigation Properties with inheritance?
How would I solve my problem then?
I think you need to add the Id to each class for PK/FK relationship
public abstract class Meter
{
....
public int PremiseId
public virtual Premise Premise { get; set; }
}
and
public class Premise
{
....
public int GasMeterId
public virtual GasMeter GasMeter{ get; set; }
}
You probably don't need to link these two entities as virtual properties within each other. Try modify Meter class to keep the PremiseId since it is the primary key of Premise table then get the Premise entity using Select(x => x.PremiseId == aMeter.PremiseId).SingleOrDefault() to get the Premise mapped to this GasMeter
public abstract class Meter
{
public int MeterId { get; set; }
public string EANNumber { get; set; }
public string MeterNumber { get; set; }
public int PremiseId { get; set; }
public abstract void AddReading(CounterReading reading);
}
SET
var aPremise = new Premise();
var aMeter = new GasMeter();
aPremise.GasMeter = aMeter;
aMeter.PremiseId = aPremise.PremiseId;
GET
var thePremise = _repository.Set<Premise>.Select(x => x.PremiseId == aMeter.PremiseId).SingleOrDefault();
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'm using CodeFirst for my devemopment. For all model classes in my Entity I have a base class named CommonFields
public class CommonFields
{
public int Status { get; set; }
public DateTime CreatedOn { get; set; }
public int CreaedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public int ModifiedBy { get; set; }
}
And, for eg. I have two classes like
public class Employee : CommonFields
{
public int Id { get; set; }
public string Name { get; set; }
//Other properties
}
public class User : CommonFields
{
public int Id { get; set; }
public string Name { get; set; }
//Other properties
}
How can I set relation from CreatedBy & ModifiedBy to User table. I just need only one directional mapping.
I need to get User information when I write objEmployee.CreatedUser
Thanks.
public class CommonFields
{
public int Status { get; set; }
public DateTime CreatedOn { get; set; }
public int? CreatedById { get; set; }
public virtual User CreatedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public virtual User ModifiedBy { get; set; }
public int? ModifiedById { get; set; }
}
and you have to define relations for all derived entities in your DbContext
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<User>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<User>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
mb.Entity<Employee>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<Employee>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
}
EDIT:
Or you could use TPH. Then your model creating look like this
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<CommonFields>().
.Map(x => x.ToTable("Users"))
.Map<User>(x => x.Requires("__type").HasValue(1)
.Map<Employee>(x => x.Requires("__type").HasValue(2);
mb.Entity<CommonFields>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<CommonFields>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
}