what's wrong with my NHibernate mapping? - c#

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);
});
}

Related

One-to-many relationship with composite primary key using EF Core

I'm trying to make a one-to-many relationship with a composite primary key:
public class Bom
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
public string Name { get; set; }
}
public class ChildReference
{
[Key]
public int Id { get; set; }
public string BomId { get; set; } // Should be the foreign key from the bom-table
public ICollection<Bom> Boms { get; set; }
}
......
builder.Entity<ChildReference>().HasKey(t => new { t.Id, t.BomId });
When I run this, Entity Framework Core creates two columns in the Bom-table called ChildReferenceBomId and ChildReferenceId. I don't want that. I want it to only create one column caled ChildReferenceId that should be the foreign key to the ChildReference table.
The reason why I want to create a composite primary key inside the ChildReference table is because I want to add rows to the table like this:
INSERT INTO ChildReference(Id, BomId) VALUES(1, '1')
INSERT INTO ChildReference(Id, BomId) VALUES(1, '2')
I'm not sure if I'm doing this the right way. Can anyone help me?
EDIT:
I basically want to do the following with entity framework core:
CREATE TABLE [dbo].BOMChildren (
[BOMChildId] [int] NOT NULL,
[BOMId] [int] NOT NULL,
CONSTRAINT [PK_BOMChildId_BOMId] PRIMARY KEY CLUSTERED
(
[BOMChildId] ASC,
[BOMId] ASC
))
CREATE TABLE [dbo].BOM (
[BOMId] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,
[BOMPartId] [nvarchar](64) NOT NULL,
[Qty] [int] NOT NULL,
[UnitOfMeasure] [nvarchar](32),
[ParentId] [int] NULL,
[ChildReference] [int] NULL,
[BOMItemDataId] [int]
)
ALTER TABLE [dbo].[BOMChildren]
ADD CONSTRAINT [FK_BOMChildren_BOM]
FOREIGN KEY([BOMId]) REFERENCES [dbo].[BOM] ([BOMId])
GO
ALTER TABLE dbo.Bom
ADD CONSTRAINT FK_Bom_BomChild
FOREIGN KEY(ChildReference, BOMId) REFERENCES [dbo].BOMChildren([BOMChildId], [BOMId])
Anyone who can push me in the right direction?
You can use set it up like this:
public class Bom
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
public string Name { get; set; }
public int ChildReferenceId { get; set; }
public ChildReference CurrentChildReference{ get; set; }
}
public class ChildReference
{
[Key]
public int Id { get; set; }
public ICollection<Bom> Boms { get; set; }
}
You can after that configure a one-to-many relationship for the above entities using Fluent API by overriding the OnModelCreating method in the context class, as shown here:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// configures one-to-many relationship
modelBuilder.Entity<Bom>()
.HasRequired<ChildReference>(s => s.CurrentChildReference)
.WithMany(g => g.Boms)
.HasForeignKey<int>(s => s.ChildReferenceId);
}
You can use the Create/Alter SQL statements that you posted above to create the tables within your database.
After that you can do a Reverse Engineer Model to generate EF code first DbContext based on your existing Database tables. You can follow the steps within chapter 3 (https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/workflows/existing-database)

How to apply correctly a relationship of 1 to 1 in EF6

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.

EF Core 3.1 DeleteBehavior.SetNull with Optional Relationship fires Constraint Exception

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
);

Cannot define a table without PK

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.

EntityFramework Fluent API Foreign Key ordering

How to set column order for foreign key properties, so all columns will be generated in my custom order rather than default alphabetical one?
I want to use pure code first aproach without any annotation attributes and I don't want to include foreign key id columns (like UserId, RoleId etc) in my entity.
Let's say I have following configuration class:
public class UserRoleEntityConfiguration: EntityTypeConfiguration<UserRole>
{
public UserRoleEntityConfiguration()
{
HasRequired(p => p.User).WithMany(p => p.Roles);
HasOptional(p => p.Company).WithMany(p => p.Users);
HasRequired(p => p.Role).WithMany(p => p.Users);
}
}
EF will generate following table:
create table [dbo].[UserRoles] (
[Id] [int] not null identity,
...
[CompanyId] [int] null,
[RoleId] [int] not null,
[UserId] [int] not null,
primary key ([Id]));
But I want:
create table [dbo].[UserRoles] (
[Id] [int] not null identity,
...
[UserId] [int] not null,
[CompanyId] [int] null,
[RoleId] [int] not null,
primary key ([Id]));
UPDATE:
Found a workaround using protected foreign key properties:
public class UserRole : AuditableEntity<int>
{
protected int? CompanyId { get; set; }
protected int RoleId { get; set; }
protected int UserId { get; set; }
public virtual Company Company { get; set; }
public virtual Role Role { get; set; }
public virtual User User { get; set; }
public class AccessExpressions
{
public static readonly Expression<Func<UserRole, int?>> CompanyId = x => x.CompanyId;
public static readonly Expression<Func<UserRole, int>> RoleId = x => x.RoleId;
public static readonly Expression<Func<UserRole, int>> UserId = x => x.UserId;
}
}
public class UserRoleEntityConfiguration: EntityTypeConfiguration<UserRole>
{
public UserRoleEntityConfiguration()
{
Property(UserRole.AccessExpressions.UserId).HasColumnOrder(8);
HasRequired(p => p.User).WithMany(p => p.Roles).HasForeignKey(UserRole.AccessExpressions.UserId);
Property(UserRole.AccessExpressions.CompanyId).HasColumnOrder(9);
HasOptional(p => p.Company).WithMany(p => p.Users).HasForeignKey(UserRole.AccessExpressions.CompanyId);
Property(UserRole.AccessExpressions.RoleId).HasColumnOrder(10);
HasRequired(p => p.Role).WithMany(p => p.Users).HasForeignKey(UserRole.AccessExpressions.RoleId);
}
}
Is there any other way to achieve the same thing?
AFAIK, without foreign key properties you can't do that.
Why do you want to omit them? You can do FK properties protected, if you don't want to set them from outside of the model.

Categories

Resources