I have a user
public class UserEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Index(IsUnique=true)]
[Required, StringLength(50)]
public string Username { get; set; }
public List<UserTokenEntity> Tokens { get; set; }
}
that has a list of tokens and I'm not sure how to set it up so that the key to UserEntityId is not null when I generate the migration. This is my TokenEntity
public class UserTokenEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(512)]
[Required]
public string TokenHash { get; set; }
[StringLength(128)]
[Required]
public string Device { get; set; }
}
when I add a migration for these entities a UserEntity_Id is created for the database on the token entity but it is a nullable int and I want nullable to be false
here is the migration generated
public override void Up()
{
CreateTable(
"dbo.UserTokenEntities",
c => new
{
Id = c.Int(nullable: false, identity: true),
TokenHash = c.String(nullable: false, maxLength: 512),
Device = c.String(nullable: false, maxLength: 128),
UserEntity_Id = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.UserEntities", t => t.UserEntity_Id)
.Index(t => t.UserEntity_Id);)
}
does anyone know how to set up the token entity to get the UserEntity_Id to be nullable: false without manually changing it?
You can explicitly define the relationship:
public class UserTokenEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(512)]
[Required]
public string TokenHash { get; set; }
[StringLength(128)]
[Required]
public string Device { get; set; }
public int UserEntityId { get; set; }
[ForeignKey("UserEntityId")]
public UserEntity UserEntity { get; set; }
}
https://msdn.microsoft.com/en-us/data/jj591583.aspx#Relationships
Related
I have a few models I am trying to bind with Include which is not returning back all expected related data. The full chain is:
User (one) > Role (one) > Permissions (Many) > Entity (One) > EntityArea (One)
These are my models: (CompanyBase is a base class with a companyId in it)
public class User : _CompanyBase
{
public int UserID { get; set; }
public string FullName { get; set; }
public int RoleID { get; set; }
public Role Role { get; set; }
}
public class Role : _CompanyBase
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int RoleID { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
public ICollection<RolePermission> RolePermissions { get; set; }
public ICollection<User> Users { get; set; }
}
public class RolePermission : _CompanyBase
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int RolePermissionID { get; set; }
public Guid Id { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string PermissionCode { get; set; }
public int RoleID { get; set; }
public Role Role { get; set; }
public int EntityID { get; set; }
public Entity Entity { get; set; }
}
public class Entity : _CompanyBase
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EntityID { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string DisplayName { get; set; }
public int EntityAreaID { get; set; }
public EntityArea EntityArea { get; set; }
}
public class EntityArea :_CompanyBase
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EntityAreaID { get; set; }
[Required]
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
public ICollection<Entity> Entities { get; set; }
}
And I am trying to bind them with:
dbUser = db.Users
.AsNoTracking()
.Where(x => x.UserId == UserID)
.Include(m => m.Role)
.ThenInclude(m => m.RolePermissions)
.ThenInclude(m => m.Entity)
.ThenInclude(m => m.EntityArea)
.FirstOrDefault();
However, I do get the Role, I'm not getting anything further (Rolepermissions collection, Entity and Area). Is there something fundamentally I am doing wrong? This is a readonly query so hence notracking being used.
Thanks!
I don't think there is anything fundamentally wrong with your attempt. Have you checked if the specific user actually has any data in the connected tables?
Include() can sometimes generate a left join when you don't really need it, so I'd advise you to stay away from it when you can. I'd generally use a projection to specify the data I want to recieve. For your example this can be done with:
dbUser = db.Users
.AsNoTracking()
.Where(x => x.UserId == UserID)
.Select(x => new
{
Role = x.Role,
RolePermissions = x.RolePermissions,
Entity = x.Entity,
EntityArea = x.EntityArea
})
.FirstOrDefault();
My entity AppUser has an optional UserProfile, and UserProfile as a required AppUser. I would like to have a foreign key to each other.
public class AppUser
{
public int Id { get; set; }
public string Name { get; set; }
public UserProfile UserProfile { get; set; }
public int? UserProfileId { get; set; }
}
public class UserProfile
{
public int Id { get; set; }
public string SomeUserProfileValue { get; set; }
public AppUser AppUser { get; set; }
public int AppUserId { get; set; }
}
I got this mapping:
modelBuilder.Entity<AppUser>().HasOptional(x => x.UserProfile).WithRequired(x => x.AppUser)
This generate the following migration. I notice there is no foreign key from AppUser to UserProfile. Also the foreignkey in UserProfile is defined on UserProfile.Id ... I want it on UserProfile.AppUserId.
public override void Up()
{
CreateTable(
"dbo.AppUsers",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
UserProfileId = c.Int(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.UserProfiles",
c => new
{
Id = c.Int(nullable: false),
SomeUserProfileValue = c.String(),
AppUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.AppUsers", t => t.Id)
.Index(t => t.Id);
}
So I tried to change the mapping configuration as follow
modelBuilder.Entity<AppUser>().HasOptional(x => x.UserProfile).WithRequired(x => x.AppUser)
.Map(c => c.MapKey("AppUserId"));
But now when I try to add the migration i get the error:
AppUserId: Name: Each property name in a type must be unique. Property name 'AppUserId' is already defined.
This seems to complain that I have a field AppUserId already defined in my model.
This is how we define our entities, we always include both the class and the id fields, gives more flexibility as to which to use under different circumstances.
So I'm a bit stuck here... is there any way to have this 1:1 bidirectional relation while having both class and the id fields defined in the model ?
And why there is no nullable foreign key generated in the AppUser table ?
I've generally found better results with DataAnnotations, myself. So:
public class AppUser
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? UserProfileId { get; set; }
[ForeignKey = "UserProfileId"]
public UserProfile UserProfile { get; set; }
}
public class UserProfile
{
[Key]
public int Id { get; set; }
public string SomeUserProfileValue { get; set; }
public int AppUserId { get; set; }
[ForeignKey = "AppUserId"]
public AppUser AppUser { get; set; }
}
I have the following classes
public class Order {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
[MaxLength(100)]
public string From { get; set; }
public int? TreatGuestEntryID { get; set; }
[ForeignKey("TreatGuestEntryID")]
public TreatedGuestEntry TreatGuestEntry { get; set; }
...
public class TreatedGuestEntry {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[MaxLength(200)]
public string Company { get; set; }
public string TypeOfTreat { get; set; }
This works as expected - in my Orders table it creates the foreign key.
Now I want to add an inverse property in TreatedGuestEntry for the order.
The best (at least somehow working) result I get when I add
modelBuilder.Entity<TreatedGuestEntry>()
.HasOptional(a => a.Order)
.WithOptionalDependent(a => a.TreatGuestEntry)
.Map(a=>a.MapKey("TreatGuestEntryID"));
and further rename the key of TreatedGuestEntry to TreatGuestEntryID.
But I get no relation in the database and also TreatGuestEntryID in the table Order is no longer a key (FK).
My approach in simple words:
In my Order I want an optional TreatedGuestEntry (and I need access to the foreign key) - and further in the related TreatedGuestEntry I want to access the Order.
In your case, the FK TreatGuestEntryID is not a PK, it means that it is a 1:n relationship. So, you have to put a Collection of Order on the other side:
public class Order
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
[MaxLength(100)]
public string From { get; set; }
public int? TreatGuestEntryID { get; set; }
public TreatedGuestEntry TreatGuestEntry { get; set; }
}
public class TreatedGuestEntry
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[MaxLength(200)]
public string Company { get; set; }
public string TypeOfTreat { get; set; }
public ICollection<Order> Orders { get; set; }
}
Mapping:
modelBuilder.Entity<Order>()
.HasOptional(i => i.TreatGuestEntry)
.WithMany(i => i.Orders)
.HasForeignKey(i => i.TreatGuestEntryID)
.WillCascadeOnDelete(false);
Generated Migration:
CreateTable(
"dbo.Orders",
c => new
{
ID = c.Int(nullable: false, identity: true),
Date = c.DateTime(nullable: false),
From = c.String(nullable: false, maxLength: 100),
TreatGuestEntryID = c.Int(),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.TreatedGuestEntries", t => t.TreatGuestEntryID)
.Index(t => t.TreatGuestEntryID);
CreateTable(
"dbo.TreatedGuestEntries",
c => new
{
ID = c.Int(nullable: false, identity: true),
Company = c.String(maxLength: 200),
TypeOfTreat = c.String(),
})
.PrimaryKey(t => t.ID);
I have 4 tables:
User table
public enum SEX { Male, Female }
public abstract class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public SEX Sex { get; set; }
}
Doctor table inherites from User
[Table("Doctor")]
public class Doctor : User
{
public string Department { get; set; }
public string Occupation { get; set; }
public string CabinetNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
Patient table inherites from User
[Table("Patient")]
public class Patient : User
{
public int InsuranceNumber { get; set; }
public int CardNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
public class Treat
{
public int TreatId { get; set; }
public int DoctorUserId { get; set; }
public int PatientUserId { get; set; }
public virtual Doctor Doctor { get; set; }
public virtual Patient Patient { get; set; }
}
public class HospitalContext: DbContext
{
public HospitalContext() : base("DBConnectionString") {
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<HospitalContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Doctor)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.DoctorUserId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Patient)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.PatientUserId)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
public DbSet<Treat> Treats { get; set; }
}
I have found much answers here but no one from them works. I have spend a few hours trying to make it work. I know that Entity Framework must enable cascade delete when there is one-to-many relation, but it didn't
Entity Framework doesn't apply cascade deletion with TPT (Table Per Type) inheritance. You can solve this with Code Fist migrations:
CreateTable(
"dbo.Treats",
c => new
{
TreatId = c.Int(nullable: false, identity: true),
DoctorUserId = c.Int(nullable: false),
PatientUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.TreatId)
.ForeignKey("dbo.Doctor", t => t.DoctorUserId, cascadeDelete: true)
.ForeignKey("dbo.Patient", t => t.PatientUserId, cascadeDelete: true)
.Index(t => t.DoctorUserId)
.Index(t => t.PatientUserId);
The important part is cascadeDelete: true. You have to manually add it after migration code generation. After that you will have cascade deletion in your database:
FOREIGN KEY ([DoctorUserId]) REFERENCES [dbo].[Doctor] ([UserID]) ON DELETE CASCADE,
FOREIGN KEY ([PatientUserId]) REFERENCES [dbo].[Patient] ([UserID]) ON DELETE CASCADE
I'm trying to configure an optional-required simple relationship, but EF doesn't seem to scaffold the right migration:
public class Applicant
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int ContactInfoId { get; set; }
public string PreferredCultureId { get; set; }
public virtual ApplicantContact Contact { get; set; }
public virtual Culture PreferredCulture { get; set; }
}
public ApplicantConfiguration()
{
HasKey(a => a.Id);
Property(a => a.FirstName).IsRequired().HasMaxLength(50);
Property(a => a.LastName).IsRequired().HasMaxLength(50);
HasOptional(a => a.Contact)
.WithRequired(c => c.Applicant)
.WillCascadeOnDelete();
HasOptional(a => a.PreferredCulture)
.WithMany()
.HasForeignKey(a => a.PreferredCultureId);
}
CreateTable(
"Main.Applicants",
c => new
{
Id = c.Int(nullable: false, identity: true),
FirstName = c.String(nullable: false, maxLength: 50),
LastName = c.String(nullable: false, maxLength: 50),
ContactInfoId = c.Int(nullable: false),
PreferredCultureId = c.String(maxLength: 128),
})
.PrimaryKey(t => t.Id)
.ForeignKey("General._Cultures", t => t.PreferredCultureId)
.Index(t => t.PreferredCultureId);
Why is ContactInfoId is not being generated as a foreign key and nullable, as it is the optional side of the relationship ?
In your domain class try
public class Applicant
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ApplicantContact Contact { get; set; }
public virtual Culture PreferredCulture { get; set; }
}
public class ContactInfo
{
// whatever contact info fields you have
}
public class Culture
{
// culture fields
}
then in your context have
public DbSet<ContactInfo> ContactInfos { get; set; }
public DbSet<Applicant> Applicants { get; set; }
public DbSet<Culture> Cultures { get; set; }
The Id fields should get automatically if they are int.