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.
Related
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)
I am creating a new Blazor server side application with EF Core 6. I am more used to using database first and Dapper. However I am trying to get more familiar with EF Core and code first approach as requested by the department.
While reviewing the documentation on the changes for EF Core 6 in relation to the many to many relationships, I am stuck on stuck on how to configure the relationships properly for what I call a nested many to many. I believe I have the basic concept of the base 2 tables with many to many but this is a little more complex. I have searched MS and SO but only found some articles that are close but not fitting my situation.
The situation is this, for example 1 table Applications, 1 table Environments and a table Databases. There are an application can have multiple environments at which point the combination of an application(s) / environment(s) can have multiple databases.
Here is how the tables were designed.
CREATE TABLE [dbo].[App] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (100) NOT NULL,
CONSTRAINT [PK_Applications] PRIMARY KEY CLUSTERED ([Id] ASC))
CREATE TABLE [dbo].[Env] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] VARCHAR (80) NOT NULL,
[Value] VARCHAR (100) NOT NULL,
CONSTRAINT [PK_Env] PRIMARY KEY CLUSTERED ([Id] ASC))
CREATE TABLE [crosswalk].[App_Env] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[AppId] INT NOT NULL,
[EnvId] INT NOT NULL,
CONSTRAINT [PK_App_Env] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_App_Env_App] FOREIGN KEY ([AppId]) REFERENCES [dbo].[App] ([Id]),
CONSTRAINT [FK_App_Env_EnvId] FOREIGN KEY ([EnvId]) REFERENCES [dbo].[Env] ([Id]))
CREATE TABLE [crosswalk].[App_Env_Db] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[AppEnvtId] INT NOT NULL,
[DbId] INT NOT NULL,
CONSTRAINT [PK_App_Env_Db] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_App_Env_Db_App_Env] FOREIGN KEY ([AppEnvtId]) REFERENCES [crosswalk].[App_Env] ([Id]),
CONSTRAINT [FK_App_Env_Db] FOREIGN KEY ([DbId]) REFERENCES [dbo].[Db] ([Id]))
The application is not complete so not able to run it. Was able to come up with one of the configurations but not sure on how to code the rest.
entity.HasMany(d => d.Envs)
.WithMany(p => p.Apps)
.UsingEntity<Dictionary<string, object>>(
"AppEnv",
l => l.HasOne<Env>().WithMany().HasForeignKey("AppId"),
r => r.HasOne<App>().WithMany().HasForeignKey("EnvId"),
j =>
{
j.HasKey("EnvId", "AppId");
j.ToTable("Crosswalk.App_Env");
j.HasIndex(new[] { "EnvId" }, "IX_AppEnv_EnvId");
j.HasIndex(new[] { "AppId" }, "IX_AppEnv_AppId");
});
The models are as follows:
[Table("App")]
public partial class App
{
public App() => Envs = new HashSet<Env>();
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
[Unicode(false)]
public string Name { get; set; }
[InverseProperty("App")]
public virtual ICollection<LookupCode> Envs { get; set; }
}
[Table("Env")]
public partial class LookupCode
{
public LookupCode() => Apps = new HashSet<Apps>();
[Key]
public int Id { get; set; }
[Required]
[StringLength(80)]
[Unicode(false)]
public string Name { get; set; }
[Required]
[StringLength(100)]
[Unicode(false)]
public string Value { get; set; }
public int SortOrder { get; set; }
[InverseProperty("Env")]
public virtual ICollection<App> Apps { get; set; }
}
[Table("Db")]
public partial class Db
{
public Db() => AppEnvDbs = new HashSet<AppEnvDb>();
[Key]
public int Id { get; set; }
[Required]
[StringLength(50)]
[Unicode(false)]
public string Name { get; set; }
[InverseProperty("Db")]
public virtual ICollection<AppEnvDb> AppEnvDbs { get; set; }
}
If this is not possible I can go back to creating the DTO's for the link tables and manually adding/creating/deleting the link records.
EDIT: Added missing table definition for Db. Although the DTO model was shown I forgot to include the data table.
CREATE TABLE [dbo].[Db] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[DbServerId] INT NOT NULL,
[Name] VARCHAR (50) NOT NULL,
CONSTRAINT [PK_Db] PRIMARY KEY CLUSTERED ([Id] ASC)
);
I have a Base class, a Derived class and Derived has a collection of Items.
I want to configure EF to delete Items when their parent Derived is deleted.
The following minimal (LinqPad) example shows how I'm trying to achieve that, but it doesn't generate the on delete cascade part, just regular FKs.
I tried [Required] attribute - didn't work.
How do I make it add delete cascade option to the FK specification?
[System.ComponentModel.DataAnnotations.Schema.Table("Bases")]
public class Base
{
public int Id {get;set;}
public string Name {get; set;}
}
[System.ComponentModel.DataAnnotations.Schema.Table("Derived")]
public class Derived : Base
{
public virtual ICollection<Item> Items {get;set;}
public Derived()
{
Items = new HashSet<Item>();
}
}
public class Item
{
public int Id {get;set;}
public int ParentId {get;set;}
public Derived Parent {get;set;}
}
public class TestDbContext : System.Data.Entity.DbContext
{
public System.Data.Entity.DbSet<Base> Bases { get; set; }
public System.Data.Entity.DbSet<Derived> Derived { get; set; }
public System.Data.Entity.DbSet<Item> Items { get; set; }
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
System.Data.Entity.Database.SetInitializer<TestDbContext>(null);
modelBuilder.Entity<Item>().HasRequired(x=>x.Parent).WithMany(x=>x.Items).HasForeignKey(x=>x.ParentId).WillCascadeOnDelete(true);
}
}
void Main()
{
var ctx = new TestDbContext();
var ddl = (ctx as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext.CreateDatabaseScript();
Console.WriteLine(ddl);
}
This is the DDL it generates:
create table [dbo].[Bases] (
[Id] [int] not null identity,
[Name] [nvarchar](max) null,
primary key ([Id])
);
create table [dbo].[Derived] (
[Id] [int] not null,
primary key ([Id])
);
create table [dbo].[Items] (
[Id] [int] not null identity,
[ParentId] [int] not null,
primary key ([Id])
);
alter table [dbo].[Derived] add constraint [Derived_TypeConstraint_From_Base_To_Derived] foreign key ([Id]) references [dbo].[Bases]([Id]);
alter table [dbo].[Items] add constraint [Item_Parent] foreign key ([ParentId]) references [dbo].[Derived]([Id]);
I have CustomDBContext:
public class CustomDBContext : DbContext
{
public CustomDBContext(string connectionString)
: base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
......
modelBuilder.Entity<KeyProd>().ToTable("KeyProd");
......
}
}
And Repository registration in Startup.cs:
.....
var builder = new ContainerBuilder();
builder.Register(c => RepositoryFactory.GetInstance<KeyProd, long>("ef")).As<IRepository<KeyProd, long>>().InstancePerRequest();
After injection of this repository to my service, i try to add new row to database:
_keyProductionRepository.Add(new KeyProd
{
Id = key.Id,
Comment = key.Comment,
CreatedBy = adminId,
DateCreated = DateTime.UtcNow,
KeyType = key.KeyType,
PageId = key.PageId,
Name = key.Name
});
The exception is:
Cannot insert the value NULL into column 'Id', table 'ln_resources.dbo.KeyProd'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated.
The model i try to insert have id (3). Is not null.I checked it in debug mode.
My table in SQl without autoincremented Id. This is a query of table:
CREATE TABLE [dbo].[KeyProd](
[Id] [bigint] NOT NULL,
[Name] [nvarchar](100) NULL,
[KeyType] [int] NOT NULL,
[Comment] [text] NULL,
[PageId] [int] NOT NULL,
[CreatedBy] [int] NOT NULL,
[DateCreated] [datetime] NOT NULL DEFAULT (getdate()),
[DateUpdated] [datetime] NULL DEFAULT (getdate()),
[UpdatedBy] [int] NULL,
PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
ALTER TABLE [dbo].[KeyProd] WITH CHECK ADD CONSTRAINT [FK_KeyProd_Page] FOREIGN KEY([PageId])
REFERENCES [dbo].[Page] ([Id])
GO
ALTER TABLE [dbo].[KeyProd] CHECK CONSTRAINT [FK_KeyProd_Page]
GO
My KeyProd Class:
public class KeyProd
{
public long Id { get; set; }
public string Name { get; set; }
public KeyType KeyType { get; set; }
public string Comment { get; set; }
public int PageId { get; set; }
public int CreatedBy { get; set; }
public DateTime DateCreated { get; set; }
public int? UpdatedBy { get; set; }
public DateTime? DateUpdated { get; set; }
}
If i try to insert this model directly from SQL SERVER by using SQL INSERT, all works fine.
Where the problem? Why Id is Null? Why mapping not working.
You may need to annotate the key field on KeyProd to indicate that you don't want the value to be automatically generated by your context. Try adding DatabaseGeneratedOption.None to your key property.
public class KeyProd {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
// ... Other properties
}
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);
});
}