I have a table called AspNetUsers which is the default table created by AspNetCore.Identity, I created a class called User which inherit IdentityUser and implement more field on AspNetUsers:
public class User : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string LockoutMessage { get; set; }
public string SessionId { get; set; }
public virtual UserDetails UserDetail { get; set; }
}
then I created another class called UserDetails, I don't need the PK in this table, because in UserDetails I need to store the details of the user available in User which is the AspNetUsers table:
public class UserDetails
{
//public string Id { get; set; }
public string Biography { get; set; }
public string Country { get; set; }
public string FacebookLink { get; set; }
public string TwitterLink { get; set; }
public string SkypeLink { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
}
Now if I uncomment the Id property the migration works well, but I don't need the Id so I commented the property and execute this command:
add-migration Initial-Migration -context DemoAppContext
update-database
inside the DemoAppContext class I told to EF to implement the FK on UserDetails:
public class DemoAppContext : IdentityDbContext<User>
{
public DemoAppContext(DbContextOptions<DemoAppContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User>(entity =>
{
entity.HasOne(d => d.UserDetail)
.WithOne(p => p.User)
.HasForeignKey<UserDetails>(x => x.UserId);
});
}
public DbSet<UserDetails> UserDetails { get; set; }
}
after the Add-Migration command I get this error:
The entity type 'UserDetails' requires a primary key to be defined.
As I said if I commented out the Id all works well, but I don't need a PK on UserDetails.
As I said if I commented out the Id all works well, but I don't need a PK on UserDetails
If I understand correctly , you just don't want to add an extra column (for example, Id or UserDetailsId ) set as the PK . The UserDetails table that you want to create will have a UserId which references the User table , and the UserId will serve as PK and FK at the same time .
In fact , there's no need to have an Id property for UserDetails entity . So why your code does not work ? The reason is that you mixed up the dependent entity and the principal entity .
The prototype of HasForeignKey<>() method is:
public virtual ReferenceReferenceBuilder<TEntity,TRelatedEntity> HasForeignKey<TDependentEntity>
(
Expression<Func<TDependentEntity,object>> foreignKeyExpression
)
where TDependentEntity : class;
Note the generic parameter of HasForeignKey<TDependentEntity>() represents the dependent entity .
And here's the code of yours :
builder.Entity<User>(entity =>
{
entity.HasOne(d => d.UserDetail)
.WithOne(p => p.User)
.HasForeignKey<UserDetails>(x => x.UserId);
});
See that ? In your code , the entity here is User , which is the Principal Entity instead of the Dependent Entity . To fix it , change your code as below :
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User>(entity =>
{
entity.HasOne(u => u.UserDetail).WithOne(d => d.User);
});
builder.Entity<UserDetails>(entity =>
{
// set the UserId as key
entity.HasKey(d=>d.UserId);
// the relationship between `UserDetails : User` is 1-to-1
entity.HasOne(d=>d.User).WithOne(u=>u.UserDetail)
// set column `UserId` as the FK for the dependent entity , i.e. , the `UserDetails` .
.HasForeignKey<UserDetails>(u=>u.UserId);
});
}
It will generate a UserDetails table with the UserId column set as the PrimaryKey and the ForeignKey at the same time :
CREATE TABLE [dbo].[UserDetails] (
[Biography] NVARCHAR (MAX) NULL,
[Country] NVARCHAR (MAX) NULL,
[FacebookLink] NVARCHAR (MAX) NULL,
[TwitterLink] NVARCHAR (MAX) NULL,
[SkypeLink] NVARCHAR (MAX) NULL,
[UserId] NVARCHAR (450) NOT NULL,
CONSTRAINT [PK_UserDetails] PRIMARY KEY CLUSTERED ([UserId] ASC),
CONSTRAINT [FK_UserDetails_AspNetUsers_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE
);
By the way , there seems a bug in SQL Server Object Explorer of Visual Studio (see my question here) , resulting in a mistake that we cannot see the already existing FK contrains .
However , we can prove it by trying to insert a row with a non-existing UserId :
insert into UserDetails
(Biography,UserId)
values
('hello,wrold','here-is-a-non-existing-user-id')
It will complains the following message as expected :
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_UserDetail_AspNetUsers_UserId". The conflict occurred in database "App-EFCore-FK-Test", table "dbo.AspNetUsers", column 'Id'.
The statement has been terminated.
Related
So i wanted to apply a relation of 1 to 1 from one table to another, with navigational properties on each one and a foreign key that is accessable on at least one of the models.
Lets suppose this example
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public virtual User User { get; set; }
}
modelBuilder.Entity<User>().HasOptional<Contact>(u=> u.Contact)
.WithRequired(c => c.User).Map(m => m.MapKey("ContactId")).
Similar to the same example used in this stack overflow question:
EF Code First - 1-to-1 Optional Relationship
The problem is that it gives an error saying that the Property name 'ContactId' is already defined.
But i want to have this foreign property defined both at the database and on the model, so that i can use for example linq:
this.dbContextProvider.CurrentContext.User.SingleOrDefault(src => src.ContactId == contactId);
or is this acceptable or very inneficient:
this.dbContextProvider.CurrentContext.User.SingleOrDefault(src => src.Contact.Id == contactId);
This last options will create a join between the two tables while query the database, right?
The downside of the correct model (i.e. without explicit User.ContactId property) is that in reality it's still a 1:n relationship. The database doesn't enforce 1:1. It's just a FK. The only way to make a true, database-enforced 1:1 association in EF6 is one in which the dependent entity (here: User) has a primary key that's also a foreign key to the principal entity (Contact):
public class User
{
public int Id { get; set; }
public string Username { get; set; }
//public int ContactId { get; set; } <= removed
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public virtual User User { get; set; }
}
And:
modelBuilder.Entity<User>()
.HasRequired<Contact>(u => u.Contact)
.WithOptional(c => c.User);
This generates the following database schema:
CREATE TABLE [dbo].[Users] (
[Id] [int] NOT NULL,
[Username] [nvarchar](max),
CONSTRAINT [PK_dbo.Users] PRIMARY KEY ([Id])
)
CREATE TABLE [dbo].[Contacts] (
[ID] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
CONSTRAINT [PK_dbo.Contacts] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_Id] ON [dbo].[Users]([Id])
ALTER TABLE [dbo].[Users] ADD CONSTRAINT [FK_dbo.Users_dbo.Contacts_Id]
FOREIGN KEY ([Id]) REFERENCES [dbo].[Contacts] ([ID])
As for querying, in a query like context.Users.Where(u => u.Contact.ID == 4), EF6 will notice that no Contact fields are requested and it will short-circuit the FK to User.Id, i.e. no join. But of course, in this setup, you may as well use context.Users.Where(u => u.Id == 4).
In EF core it would be possible to use your model, with User.ContactId, by this mapping:
modelBuilder.Entity<User>()
.HasOne(u => u.Contact)
.WithOne(c => c.User)
.HasForeignKey<User>(u => u.ContactId);
EF core is smart enough to create a unique index on User.ContactId, so this is a database-enforced 1:1 association with a separate FK.
I'm using EF Core 3.1 DB First, trying to set DeleteBehavior.SetNull for an optional relationship, but I'm still getting an exception on SaveChanges.
The entities are: Patient which has a nullable realtionship to Address in a field called Address.
The entities were generated by scaffold-DbContext cmd like this (I removed properties irrelevant for the question):
public partial class Address
{
public Address()
{
Patient = new HashSet<Patient>();
}
public int Id { get; set; }
...
public virtual ICollection<Patient> Patient { get; set; }
}
And Patient class:
public partial class Patient
{
public int Code { get; set; }
public int? Address { get; set; }
...
public virtual Address AddressNavigation { get; set; }
}
In OnModelCreating function I have the following piece of code:
modelBuilder.Entity<Patient>(entity =>
{
...
entity.HasOne(d => d.AddressNavigation)
.WithMany(p => p.Patient)
.HasForeignKey(d => d.Address)
.HasConstraintName("FK__Patient__Address__2F10007B")
.OnDelete(DeleteBehavior.SetNull);
});
When I try to remove an Address, with the following lines of code:
using (ClinicContext ctx = new ClinicContext())
{
var address = ctx.Address.Find(addressId);
ctx.Remove(address);
ctx.SaveChanges();
}
I get the following exception:
The DELETE statement conflicted with the REFERENCE constraint
"FK__Patient__Address__2F10007B". The conflict occurred in database
"C:\USERS\USER\SOURCE\REPOS\CLINIC\DB\CLINIC.MDF", table
"dbo.Patient", column 'Address'. The statement has been terminated
What am I missing here?
As #Jerry said, since you are using DB First, Add DeleteBehavior.SetNull in the generated DbContext will have no effect to your database after you do the scaffold-DbContext commond.
You'd better recreate your table with the below statement:
CREATE TABLE [dbo].[Patient] (
[Code] INT IDENTITY (1, 1) NOT NULL,
[Id] VARCHAR (9) NOT NULL,
[Address] INT NULL,
PRIMARY KEY CLUSTERED ([Code] ASC),
FOREIGN KEY ([Address]) REFERENCES [dbo].[Address] ([Id]) ON DELETE SET NULL
);
I am pretty new to asp.net MVC 5 and entityframework 6
I keep getting the above error when I try to create a controller with views
Here is my model I am trying to create a view for:
public class Goal
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int GoalId { get; set; }
[StringLength(120, ErrorMessage = "Max length is 120 and Min length is {0}", MinimumLength = 4)]
public string Title { get; set; }
public GoalStatus Status { get; set; }
public string OwnerUserId { get; set; }
public ApplicationUser OwnerUser { get; set; }
public List<GoalPlan> Plans { get; set; }
}
here is my dbcontext class code
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public virtual DbSet<Goal> Goals { get; set; }
public virtual DbSet<GoalPlan> GoalPlans { get; set; }
public virtual DbSet<GoalTask> GoalTasks { get; set; }
public virtual DbSet<LinkableItem> LinkableItems { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
}
when I comment out the OwnerUserId lines it works successfully.
The database is created.
I re-built the project each time I changed the code before creating the controller.
This also fails if I use the [ForeignKey("OwnerUser")] attribute on OwnerUserId.
Can anyone help me with why I am getting the metadata error?
id like to keep learning c# mvc.
EDIT...
so I changed OwnerUserId to int and that allowed the scaffolding to work. but the problem is not solved...
AspNetUsers is created with the following SQL by the code first initial code migration....
CREATE TABLE [dbo].[AspNetUsers] (
[Id] NVARCHAR (128) NOT NULL,
[Email] NVARCHAR (256) NULL,
[EmailConfirmed] BIT NOT NULL,
the line in the goal/index method is
var items = await db.Goals.Include(x => x.OwnerUser).ToListAsync();
in the view is
Html.DisplayFor(modelItem => item.OwnerUser.UserName)
and finally which is the issue, the goal table is created as follows
CREATE TABLE [dbo].[Goals] (
[GoalId] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (120) NULL,
[Status] INT NOT NULL,
[OwnerUserId] INT NOT NULL,
[OwnerUser_Id] NVARCHAR (128) NULL,
CONSTRAINT [PK_dbo.Goals] PRIMARY KEY CLUSTERED ([GoalId] ASC),
CONSTRAINT [FK_dbo.Goals_dbo.AspNetUsers_OwnerUser_Id] FOREIGN KEY
([OwnerUser_Id]) REFERENCES [dbo].[AspNetUsers] ([Id])
);
the value gets stored in OwnerUserId;
thanks again;
ok so here is the answer to my own question
the goal properties should be
[ForeignKey("OwnerUser")]
public string OwnerUserId { get; set; }
public ApplicationUser OwnerUser { get; set; }
as was in my original code, including the annotation, but Im not sure if its requried....
then the ApplicationDbContext was updated with the following fluent
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Entity<Goal>()
.HasRequired(x => x.OwnerUser)
.WithMany(x => x.Goals)
.HasForeignKey(x => x.OwnerUserId);
modelBuilder.Entity<GoalTask>()
.HasRequired(x => x.AssignedToUser)
.WithMany(x => x.GoalTasks)
.HasForeignKey(x => x.AssignedToUserId);
}
database table gets created correctly with a string.
The controller scaffolding works
The index method correctly displays the username.
Actually I know how to create and use many-to-many relation in regular EF Core DB context, but in my present project I decided to migrate my Identity DB context tables to my ApplicationDbContext, where I keep rest of my business tables:
public class ApplicationDbContext : IdentityDbContext<AppUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
public DbSet<CompanyModel> Companies { get; set; }
public DbSet<CompanyUser> CompaniesUsers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//many-to-many
modelBuilder.Entity<CompanyUser>()
.HasKey(c => new { c.CompanyId, c.UserId });
/* do I realy need this at all? -> https://stackoverflow.com/a/54612387/12603542
modelBuilder.Entity<CompanyUser>()
.HasOne(cu => cu.AppUser)
.WithMany(au => au.CompaniesUsers)
.HasForeignKey(cu => cu.UserId);
modelBuilder.Entity<CompanyUser>()
.HasOne(cu => cu.CompanyModel)
.WithMany()
.HasForeignKey(cu => cu.CompanyId);
*/
}
}
I have a relations, where many users may have many companies, so I created Company <-> AppUser many-to-many bridge:
public class CompanyUser
{
public int CompanyId { get; set; }
public string UserId { get; set; }
public AppUser AppUser { get; set; }
public CompanyModel CompanyModel { get; set; }
}
And my AppUser contains navigation property:
public class AppUser : IdentityUser
{
//(...)
public ICollection<CompanyUser> CompaniesUsers { get; set; } //many-to-many
}
Same about my CompanyModel:
public class CompanyModel
{
[Key]
public int CompanyId { get; set; }
//(...)
public ICollection<CompanyUser> CompaniesUsers { get; set; } //many-to-many
}
Everything seems to be fine, until I am not trying to retrieve an user with related companies drom DB, where my controller calls DAL method:
AppUser userWithLists = _repositoryUser.GetUser(user.Id);
And in my repository I try to do that:
public AppUser GetUser(string id)
{
return _context.Users
.Include(u => u.CompaniesUsers)
.ThenInclude(u => u.CompanyModel)
.Where(u => u.Id == id)
.FirstOrDefault();
}
But unfortunately when executing LINQ query on my DB context I am receiving an exception:
SqlException: Invalid column name 'AppUserId'. Invalid column name
'AppUserId'.
With commented out OnModelCreating code I am getting another exception:
SqlException: Invalid column name 'CompanyModelCompanyId'.
Bridge table T-SQL creation code:
CREATE TABLE [dbo].[CompaniesUsers] (
[CompanyId] INT NOT NULL,
[UserId] NVARCHAR (450) NOT NULL,
CONSTRAINT [PK_CompaniesUsers] PRIMARY KEY CLUSTERED ([CompanyId] ASC, [UserId] ASC),
CONSTRAINT [FK_CompaniesUsers_Companies_CompanyId] FOREIGN KEY ([CompanyId]) REFERENCES [dbo].[Companies] ([CompanyId]) ON DELETE CASCADE,
CONSTRAINT [FK_CompaniesUsers_AppUser_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE
);
GO
CREATE NONCLUSTERED INDEX [IX_CompaniesUsers_CompanyId]
ON [dbo].[CompaniesUsers]([CompanyId] ASC);
I have a table PayrollProcessingProgress which has as its primary key, the id of another table, PayrollPeriod. It is not a one-to-one mapping as a PayrollPeriod has either 1 or zero PayrollProcessingProgresses.
my mapping
public class PayrollProcessingProgressMap : ClassMap<PayrollProcessingProgress>
{
public PayrollProcessingProgressMap()
{
Table("PayrollProcessingProgress");
Id(x => x.PayrollPeriodId).GeneratedBy.Foreign("PayrollPeriodId");
Map(x => x.RecordsProcessed);
}
}
for table
CREATE TABLE [dbo].[PayrollProcessingProgress](
[PayrollPeriodId] [uniqueidentifier] NOT NULL,
[RecordsProcessed] [int] NOT NULL,
CONSTRAINT [PK_PayrollProcessingProgress] PRIMARY KEY CLUSTERED
(
[PayrollPeriodId] ASC
)
)
GO
ALTER TABLE [dbo].[PayrollProcessingProgress] WITH CHECK ADD CONSTRAINT [FK_PayrollProcessingProgress_PayrollPeriods] FOREIGN KEY([PayrollPeriodId])
REFERENCES [dbo].[PayrollPeriods] ([Id])
GO
ALTER TABLE [dbo].[PayrollProcessingProgress] CHECK CONSTRAINT [FK_PayrollProcessingProgress_PayrollPeriods]
GO
I can Read<> and update the number fields on the entities I've saved manually in the db successfully, but when I try to
Save(new PayrollProcessingProgress{
PayrollPeriodId = [Guid I know to be a valid payrollperiodid],
RecordsProcessed = 0
}
I get "Unable to resolve property: PayrollPeriodId"
you have to define a reference to the main entity to use the Id from or define it as simple keyreference
public PayrollProcessingProgress
{
public virtual PayrollPeriod Period { get; set; }
public virtual int RecordsProcessed { get; set; }
}
public PayrollProcessingProgressMap()
{
CompositeId().KeyReference(x => x.Period, "PayrollPeriodId");
Map(x => x.RecordsProcessed);
}
another possibility is to map it as nullable property in the main class ans use join mapping. However you can not delete the RecordsProcessed record anymore
public PayrollPeriod
{
...
public virtual int? RecordsProcessed { get; set; }
}
public PayrollPeriodMap()
{
Join("PayrollProcessingProgress", join =>
{
join.KeyColumn("PayrollPeriodId");
join.Optional();
join.Map(x => x.RecordsProcessed);
});
}