Ok this is driving me insane.
I have tried to build a few tables using Code First but I cannot get it to do what I have asked.
So I have used SQL Management studio to create my tables which I will post now:
CREATE TABLE [dbo].[Products](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nchar](10) NULL,
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
and for the product detail I have
CREATE TABLE [dbo].[ProductDetail](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Title] [nchar](10) NULL,
[Summary] [nchar](10) NULL,
[ProductId] [int] NOT NULL,
CONSTRAINT [PK_ProductDetail] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[ProductDetail] WITH CHECK ADD CONSTRAINT [FK_ProductDetail_Products] FOREIGN KEY([ProductId])
REFERENCES [dbo].[Products] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[ProductDetail] CHECK CONSTRAINT [FK_ProductDetail_Products]
GO
As you can see, if I delete a Product then the ProductDetails will also be deleted.
But if I delete my ProductDetails it does not cascade.
For some reason I am having great difficulty in replicating that in Code First EF.
Can someone help me please?
I've always generate my code first domain classes and mappings using Entity Framework Power Tools and it works great. I've used this tool to generate code first classes on the base of your two tables, posted above. Here they are:
public partial class Product
{
public Product()
{
this.ProductDetails = new List<ProductDetail>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductDetail> ProductDetails { get; set; }
}
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name)
.IsFixedLength()
.HasMaxLength(10);
// Table & Column Mappings
this.ToTable("Products");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Name).HasColumnName("Name");
}
}
public partial class ProductDetail
{
public int Id { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
public class ProductDetailMap : EntityTypeConfiguration<ProductDetail>
{
public ProductDetailMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Title)
.IsFixedLength()
.HasMaxLength(10);
this.Property(t => t.Summary)
.IsFixedLength()
.HasMaxLength(10);
// Table & Column Mappings
this.ToTable("ProductDetail");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Title).HasColumnName("Title");
this.Property(t => t.Summary).HasColumnName("Summary");
this.Property(t => t.ProductId).HasColumnName("ProductId");
// Relationships
this.HasRequired(t => t.Product)
.WithMany(t => t.ProductDetails)
.HasForeignKey(d => d.ProductId);
}
}
Related
In a SQL Server database, I have two almost identical tables TBL_1 and TBL_2.
Here are the create scripts for both tables:
TBL_1:
CREATE TABLE [dbo].[TBL_1](
[ID] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](100) NOT NULL,
[value_type_ID] [int] NOT NULL,
[description] [nvarchar](150) NOT NULL,
CONSTRAINT [PK_TBL_1] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[TBL_1] ADD CONSTRAINT [DF__TBL_1__param__40F9A68C] DEFAULT ('-') FOR [name]
GO
ALTER TABLE [dbo].[TBL_1] ADD CONSTRAINT [DF_TBL_1_description] DEFAULT ('-') FOR [description]
GO
ALTER TABLE [dbo].[TBL_1] WITH CHECK ADD CONSTRAINT [TBL_1_TBL_value_types] FOREIGN KEY([value_type_ID])
REFERENCES [dbo].[TBL_Value_Type_Definition] ([value_type_ID])
GO
ALTER TABLE [dbo].[TBL_1] CHECK CONSTRAINT [TBL_1_TBL_value_types]
GO
TBL_2:
CREATE TABLE [dbo].[TBL_2](
[ID] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](100) NOT NULL,
[value_type_ID] [int] NOT NULL,
CONSTRAINT [PK_TBL_2] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[TBL_2] ADD CONSTRAINT [DF_TBL_2_name] DEFAULT ('-') FOR [name]
GO
ALTER TABLE [dbo].[TBL_2] WITH CHECK ADD CONSTRAINT [TBL_2_TBL_value_types] FOREIGN KEY([value_type_ID])
REFERENCES [dbo].[TBL_Value_Type_Definition] ([value_type_ID])
GO
ALTER TABLE [dbo].[TBL_2] CHECK CONSTRAINT [TBL_2_TBL_value_types]
GO
After executing Scaffold-DbContext, the following classes were generated for these two tables:
public partial class Tbl2
{
public Tbl2()
{
Tbl2Translations = new HashSet<Tbl2Translation>();
}
public int Id { get; set; }
public string Name { get; set; }
public int ValueTypeId { get; set; }
public virtual ICollection<Tbl2Translation> Tbl2Translations { get; set; }
}
public partial class Tbl1
{
public Tbl1()
{
Tbl1Translations = new HashSet<Tbl1Translation>();
}
public int Id { get; set; }
public string Name { get; set; }
public int ValueTypeId { get; set; }
public string Description { get; set; }
public virtual TblValueTypeDefinition ValueType { get; set; }
public virtual ICollection<Tbl1Translation> Tbl1Translations { get; set; }
}
For some reason, a related ValueType is generated for Tbl1, but not for Tbl2. I've already compared the tables in the database but I cannot find any obvious reason for why the related ValueType is missing in Tbl2. This is very frustrating because I have no starting point for debugging this issue so it basically feels like searching for a needle in a haystack. Here is the generated TblValueTypeDefinition class:
public partial class TblValueTypeDefinition
{
public TblValueTypeDefinition()
{
Tbl2s = new HashSet<Tbl2>();
}
public int ValueTypeId { get; set; }
public string Name { get; set; }
public string Unit { get; set; }
public string Type { get; set; }
public virtual ICollection<Tbl2> Tbl2s { get; set; }
}
And here is the create script for this table:
CREATE TABLE [dbo].[TBL_Value_Type_Definition](
[value_type_ID] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](50) NOT NULL,
[unit] [nvarchar](10) NOT NULL,
[type] [nvarchar](10) NOT NULL,
CONSTRAINT [PK_TBL_Value_Type_Definition] PRIMARY KEY CLUSTERED
(
[value_type_ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[TBL_Value_Type_Definition] ADD DEFAULT ('-') FOR [name]
GO
ALTER TABLE [dbo].[TBL_Value_Type_Definition] ADD DEFAULT ('-') FOR [unit]
GO
ALTER TABLE [dbo].[TBL_Value_Type_Definition] ADD DEFAULT ('-') FOR [type]
GO
What could be the reason why the related entity is missing in one of the tables?
Table 1 has an error in table name REFERENCES [dbo].[TBL_value_types] instead of REFERENCES [dbo].[TBL_Value_Type_Definition]
Table2 has the same error (Table name). REFERENCES [dbo].[TBL_value_types] instead of REFERENCES [dbo].[TBL_Value_Type_Definition]
The tables are created but the references are broken. No foreign keys are created.
Fixed code - Sql script
CREATE TABLE [dbo].[TBL_Value_Type_Definition](
[value_type_ID] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](50) NOT NULL,
[unit] [nvarchar](10) NOT NULL,
[type] [nvarchar](10) NOT NULL,
CONSTRAINT [PK_TBL_Value_Type_Definition] PRIMARY KEY CLUSTERED
(
[value_type_ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[TBL_Value_Type_Definition] ADD DEFAULT ('-') FOR [name]
GO
ALTER TABLE [dbo].[TBL_Value_Type_Definition] ADD DEFAULT ('-') FOR [unit]
GO
ALTER TABLE [dbo].[TBL_Value_Type_Definition] ADD DEFAULT ('-') FOR [type]
GO
CREATE TABLE [dbo].[TBL_1](
[ID] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](100) NOT NULL,
[value_type_ID] [int] NOT NULL,
[description] [nvarchar](150) NOT NULL,
CONSTRAINT [PK_TBL_1] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[TBL_1] ADD CONSTRAINT [DF__TBL_1__param__40F9A68C] DEFAULT ('-') FOR [name]
GO
ALTER TABLE [dbo].[TBL_1] ADD CONSTRAINT [DF_TBL_1_description] DEFAULT ('-') FOR [description]
GO
ALTER TABLE [dbo].[TBL_1] WITH CHECK ADD CONSTRAINT [TBL_1_TBL_value_types] FOREIGN KEY([value_type_ID])
REFERENCES [dbo].[TBL_Value_Type_Definition] ([value_type_ID])
GO
ALTER TABLE [dbo].[TBL_1] CHECK CONSTRAINT [TBL_1_TBL_value_types]
GO
CREATE TABLE [dbo].[TBL_2](
[ID] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](100) NOT NULL,
[value_type_ID] [int] NOT NULL,
CONSTRAINT [PK_TBL_2] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[TBL_2] ADD CONSTRAINT [DF_TBL_2_name] DEFAULT ('-') FOR [name]
GO
ALTER TABLE [dbo].[TBL_2] WITH CHECK ADD CONSTRAINT [TBL_2_TBL_value_types] FOREIGN KEY([value_type_ID])
REFERENCES [dbo].[TBL_Value_Type_Definition] ([value_type_ID])
GO
ALTER TABLE [dbo].[TBL_2] CHECK CONSTRAINT [TBL_2_TBL_value_types]
GO
Scaffold-DbContext from Microsoft.EntityFrameworkCore.Tools nuget package
Scaffold-DbContext -Connection "Data Source=localhost; Initial Catalog=SO1;User ID=sa;Password=100;Encrypt=False" -Provider Microsoft.EntityFrameworkCore.SqlServer
GeneratedCode
SO1Context.cs
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
namespace EfCoreMissingRelated
{
public partial class SO1Context : DbContext
{
public SO1Context()
{
}
public SO1Context(DbContextOptions<SO1Context> options)
: base(options)
{
}
public virtual DbSet<Tbl1> Tbl1s { get; set; } = null!;
public virtual DbSet<Tbl2> Tbl2s { get; set; } = null!;
public virtual DbSet<TblValueTypeDefinition> TblValueTypeDefinitions { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
optionsBuilder.UseSqlServer("Data Source=localhost; Initial Catalog=SO1;User ID=sa;Password=100;Encrypt=False");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Tbl1>(entity =>
{
entity.ToTable("TBL_1");
entity.Property(e => e.Id).HasColumnName("ID");
entity.Property(e => e.Description)
.HasMaxLength(150)
.HasColumnName("description")
.HasDefaultValueSql("('-')");
entity.Property(e => e.Name)
.HasMaxLength(100)
.HasColumnName("name")
.HasDefaultValueSql("('-')");
entity.Property(e => e.ValueTypeId).HasColumnName("value_type_ID");
entity.HasOne(d => d.ValueType)
.WithMany(p => p.Tbl1s)
.HasForeignKey(d => d.ValueTypeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("TBL_1_TBL_value_types");
});
modelBuilder.Entity<Tbl2>(entity =>
{
entity.ToTable("TBL_2");
entity.Property(e => e.Id).HasColumnName("ID");
entity.Property(e => e.Name)
.HasMaxLength(100)
.HasColumnName("name")
.HasDefaultValueSql("('-')");
entity.Property(e => e.ValueTypeId).HasColumnName("value_type_ID");
entity.HasOne(d => d.ValueType)
.WithMany(p => p.Tbl2s)
.HasForeignKey(d => d.ValueTypeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("TBL_2_TBL_value_types");
});
modelBuilder.Entity<TblValueTypeDefinition>(entity =>
{
entity.HasKey(e => e.ValueTypeId);
entity.ToTable("TBL_Value_Type_Definition");
entity.Property(e => e.ValueTypeId).HasColumnName("value_type_ID");
entity.Property(e => e.Name)
.HasMaxLength(50)
.HasColumnName("name")
.HasDefaultValueSql("('-')");
entity.Property(e => e.Type)
.HasMaxLength(10)
.HasColumnName("type")
.HasDefaultValueSql("('-')");
entity.Property(e => e.Unit)
.HasMaxLength(10)
.HasColumnName("unit")
.HasDefaultValueSql("('-')");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}
Tbl1.cs
using System;
using System.Collections.Generic;
namespace EfCoreMissingRelated
{
public partial class Tbl1
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public int ValueTypeId { get; set; }
public string Description { get; set; } = null!;
public virtual TblValueTypeDefinition ValueType { get; set; } = null!;
}
}
Tbl2.cs
using System;
using System.Collections.Generic;
namespace EfCoreMissingRelated
{
public partial class Tbl2
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public int ValueTypeId { get; set; }
public virtual TblValueTypeDefinition ValueType { get; set; } = null!;
}
}
TblValueTypeDefinition.cs
using System;
using System.Collections.Generic;
namespace EfCoreMissingRelated
{
public partial class TblValueTypeDefinition
{
public TblValueTypeDefinition()
{
Tbl1s = new HashSet<Tbl1>();
Tbl2s = new HashSet<Tbl2>();
}
public int ValueTypeId { get; set; }
public string Name { get; set; } = null!;
public string Unit { get; set; } = null!;
public string Type { get; set; } = null!;
public virtual ICollection<Tbl1> Tbl1s { get; set; }
public virtual ICollection<Tbl2> Tbl2s { get; set; }
}
}
I looked through many similar questions, but I find no applicable solution.
I get the following error message during tests:
System.InvalidOperationException : The relationship from 'Product.FeatureType' to 'FeatureType.Product' with foreign key properties {'Type' : string} cannot target the primary key {'Id' : Guid} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.
The foreign key should be FeatureType's Type field.
This only happens, when I set the type of Product.Type as a string and as not as a Guid. But it should be a string, rather than a Guid. I do not understand at all what is the problem here. I do the project in a DB-first approach and the database can be created without a problem with SQL using this logic. I appreciate every help.
Edit: Here is my MSSQL model:
CREATE TABLE [Core].[FeatureType](
[id] [uniqueidentifier] NOT NULL,
[int_id] [int] IDENTITY(1,1) NOT NULL,
[type] [varchar](50) NOT NULL UNIQUE,
[description] [uniqueidentifier] NULL,
[SysStartTime] [datetime2](7) GENERATED ALWAYS AS ROW START NOT NULL,
[SysEndTime] [datetime2](7) GENERATED ALWAYS AS ROW END NOT NULL,
CONSTRAINT [PK_FeatureType] PRIMARY KEY NONCLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime])
) ON [PRIMARY]
WITH
(
SYSTEM_VERSIONING = ON ( HISTORY_TABLE = [Core].[FeatureTypeHistory] )
)
GO
CREATE TABLE [Core].[Product](
[id] [uniqueidentifier] NOT NULL,
[name] [varchar](100) NOT NULL UNIQUE,
[type] [varchar](50) NOT NULL FOREIGN KEY REFERENCES [Core].[FeatureType](type),
[SysStartTime] [datetime2](7) GENERATED ALWAYS AS ROW START NOT NULL,
[SysEndTime] [datetime2](7) GENERATED ALWAYS AS ROW END NOT NULL,
CONSTRAINT [PK_Product] PRIMARY KEY NONCLUSTERED ([id] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY],
PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime])
) ON [PRIMARY] WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = [Core].[ProductHistory] ))
GO
In my understanding, this should work because the FeatureType table's Type column is UNIQUE.
I have the following models:
public class Product : IDBModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public DateTime SysStartTime { get; set; }
public DateTime SysEndTime { get; set; }
public FeatureType FeatureType { get; }
}
public class FeatureType : IDBModel
{
public Guid Id { get; set; }
public string Type { get; set; }
public Guid? Description { get; set; }
public DateTime SysStartTime { get; set; }
public DateTime SysEndTime { get; set; }
public TxtID TxtIDDescription { get; set; }
public ICollection<Feature> Feature { get; }
public ICollection<Product> Product { get; }
}
And the following related context configuration:
public virtual DbSet<Product> Product { get; set; }
public virtual DbSet<FeatureType> FeatureType { get; set; }
...
modelBuilder.Entity<Product>(entity =>
{
entity.ToTable("Product", CoreSchema)
.HasKey(k => new { k.Id })
.HasName("PK_Product");
entity.Property(a => a.Id).HasColumnName("id");
entity.Property(a => a.Name).HasColumnName("name").IsRequired();
entity.Property(a => a.Type).HasColumnName("type").HasMaxLength(50).IsRequired().IsUnicode(false);
entity.Property(e => e.SysStartTime).HasColumnName("SysStartTime").ValueGeneratedOnAddOrUpdate();
entity.Property(e => e.SysEndTime).HasColumnName("SysEndTime").ValueGeneratedOnAddOrUpdate();
entity.HasOne(p => p.FeatureType)
.WithMany(d => d.Product)
.HasForeignKey(p => p.Type)
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity<FeatureAttributeSet>(entity =>
{
entity.ToTable("FeatureAttributeSet", "Core")
.HasKey(e => new { e.Id })
.HasName("PK_FeatureAttributeSet");
entity.Property(e => e.Id).HasColumnName("id").IsRequired();
entity.Property(e => e.AttributeSetId).HasColumnName("as_id").IsRequired();
entity.Property(e => e.FeatureId).HasColumnName("feature_id").IsRequired();
entity.Property(e => e.SysStartTime).ValueGeneratedOnAddOrUpdate();
entity.Property(e => e.SysEndTime).ValueGeneratedOnAddOrUpdate();
entity.HasOne(d => d.AttributeSet)
.WithMany(p => p.FeatureAttributeSet)
.HasForeignKey(d => d.AttributeSetId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_FeatureAttributeSet_AttributeSet");
entity.HasOne(d => d.Feature)
.WithMany(p => p.FeatureAttributeSet)
.HasForeignKey(d => d.FeatureId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_FeatureAttributeSet_Feature");
});
You can model this in EF by using a value conversion.
The first step is to change the type of Product.Type:
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime SysStartTime { get; set; }
public DateTime SysEndTime { get; set; }
public Guid Type { get; set; } // <= Guid, not string
public FeatureType FeatureType { get; }
}
This satisfies EF's requirement that primary key and foreign key properties should have the same type.
But that alone would generate SQL that throws an exception because the database type is different. That's where value conversion are helpful. Just add one line in OnModelCreating:
entity.ToTable("Product")
.HasKey(k => new { k.Id })
.HasName("PK_Product");
// To convert string value from the database:
entity.Property(e => e.Type).HasConversion<string>();
...
Now EF accepts the association and also knows how to generate correct SQL for queries and inserts.
The solution is to configure the PrincipalKey. The PrincipalKey will allow us to define the reference key with a unique restriction which will be the destination of the relationship.
So you can use like this
modelBuilder.Entity<Product>(entity =>
{
entity.ToTable("Product", CoreSchema)
.HasKey(k => new { k.Id })
.HasName("PK_Product");
entity.Property(a => a.Id).HasColumnName("id");
entity.Property(a => a.Name).HasColumnName("name").IsRequired();
entity.Property(a => a.Type).HasColumnName("type").HasMaxLength(50).IsRequired().IsUnicode(false);
entity.Property(e => e.SysStartTime).HasColumnName("SysStartTime").ValueGeneratedOnAddOrUpdate();
entity.Property(e => e.SysEndTime).HasColumnName("SysEndTime").ValueGeneratedOnAddOrUpdate();
entity.HasOne(p => p.FeatureType)
.WithMany(d => d.Product)
.HasPrincipalKey(p => p.Type)
.HasForeignKey(p => p.Type)
.OnDelete(DeleteBehavior.Restrict);
});
The foreign key that you specify in your model builder statement for the FeatureType of Product in .HasForeignKey(p => p.Type) must be the same type as the primary key of FeatureType. Because the Id property of FeatureType is a Guid, Type on Product must also be of type Guid to be used as the foreign key for the relationship with FeatureType.
Is there a reason that you can't change the type of the Type property to Guid?
I have the following mapping defined via FluentMapping
public class RuleMap : ClassMap<Rule>
{
public RuleMap()
{
Table("NEW");
Id(x => x.Id, "Id").Not.Nullable();
Map(x => x.SenderId, "SenderId").Nullable();
}
}
for the following class
public class Rule
{
public virtual int Id { get; set; }
public virtual int? SenderId { get; set; }
}
My Table in Database is defined as
USE [Test]
GO
/****** Object: Table [dbo].[NEW] Script Date: 23.04.2015 22:14:53 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[NEW](
[Id] [int] NOT NULL,
[SenderId] [int] NULL,
CONSTRAINT [PK_NEW] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
and has a single Entry with
Id | SenderId
1 | `NULL`
Now when I load all Rules via my SessionFactory
var rules = sessionFactory.QueryOvery<Rule>.List();
the list contains 1 Item (so far so good), but the property SenderId has the value 0 and not NULL what I have expected.
So what am I doing wrong here?
Using NHibernate 3.1 and FluentNHibernate 1.2.
Change your mapping to,
public class NullableMap : ClassMap<Nullable>
{
public NullableMap()
{
Table("nullable");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("id");
Map(x => x.SenderId, "SenderId").Nullable().Default(null);
}
}
where default value is set to null (.Default(null);)
{"Cannot insert the value NULL into column 'RootID', table 'Legacy.dbo.Middle'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated."}
I have a Root class, Middle class, and 'bottom' class. The root class as "Middle" as children, then Middle has "Bottom" as children. However, in this legacy database with poor design there is also a reference to "Root" from the "Bottom".
root table
CREATE TABLE [dbo].[Root](
[RootID] [int] IDENTITY(1,1) NOT NULL,
[RootName] [varchar](max) NOT NULL,
CONSTRAINT [PK_Root] PRIMARY KEY CLUSTERED
(
[RootID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
middle table
CREATE TABLE [dbo].[Middle](
[MiddleID] [int] IDENTITY(1,1) NOT NULL,
[MiddleName] [varchar](max) NOT NULL,
[RootID] [int] NOT NULL,
CONSTRAINT [PK_Middle] PRIMARY KEY CLUSTERED
(
[MiddleID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
bottom table
CREATE TABLE [dbo].[Bottom](
[BottomID] [int] IDENTITY(1,1) NOT NULL,
[BottomName] [varchar](max) NOT NULL,
[MiddleID] [int] NOT NULL,
[RootID] [int] NOT NULL,
CONSTRAINT [PK_Bottom] PRIMARY KEY CLUSTERED
(
[BottomID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
the foreign keys
ALTER TABLE [dbo].[Middle] WITH CHECK ADD CONSTRAINT [FK_Middle_Root] FOREIGN KEY([RootID])
REFERENCES [dbo].[Root] ([RootID])
GO
ALTER TABLE [dbo].[Middle] CHECK CONSTRAINT [FK_Middle_Root]
ALTER TABLE [dbo].[Bottom] WITH CHECK ADD CONSTRAINT [FK_Bottom_Middle] FOREIGN KEY([MiddleID])
REFERENCES [dbo].[Middle] ([MiddleID])
GO
ALTER TABLE [dbo].[Bottom] CHECK CONSTRAINT [FK_Bottom_Middle]
GO
ALTER TABLE [dbo].[Bottom] WITH CHECK ADD CONSTRAINT [FK_Bottom_Root] FOREIGN KEY([RootID])
REFERENCES [dbo].[Root] ([RootID])
GO
ALTER TABLE [dbo].[Bottom] CHECK CONSTRAINT [FK_Bottom_Root]
c# entities
public class Root
{
public Root()
{
Middles = new SortedSet<Middle>();
}
public int RootID { get; set; }
public string RootName { get; set; }
public ISet<Middle> Middles { get; set; }
}
public class Middle
{
public Middle()
{
Bottoms = new SortedSet<Bottom>();
}
public int MiddleID { get; set; }
public Root Root
{
get;
set;
}
public string MiddleName { get; set; }
public ISet<Bottom> Bottoms { get; set; }
}
public class Bottom
{
public int BottomID { get; set; }
public Root Root { get; set; }
public Middle Middle { get; set; }
public string BottomName { get; set; }
}
I am using the following mappings via FluentNHibernate, but I've tried a ton of variations and just cannot get it to work in any way.
public class RootMap : IAutoMappingOverride<Root>
{
public void Override(AutoMapping<Root> mapping)
{
mapping.Not.LazyLoad();
mapping.Id(x => x.RootID);
mapping.HasMany(x => x.Middles).KeyColumn("RootID").Cascade.AllDeleteOrphan().Inverse();
}
}
public class MiddleMap : IAutoMappingOverride<Middle>
{
public void Override(AutoMapping<Middle> mapping)
{
mapping.Not.LazyLoad();
mapping.Id(x => x.MiddleID);
mapping.References(x => x.Root);
mapping.HasMany(x => x.Bottoms).KeyColumn("MiddleID").Cascade.AllDeleteOrphan();
}
}
public class BottomMap : IAutoMappingOverride<Bottom>
{
public void Override(AutoMapping<Bottom> mapping)
{
mapping.Not.LazyLoad();
mapping.Id(x => x.BottomID);
mapping.References(x => x.Root);
mapping.References(x => x.Middle);
}
}
You need to declare the many valued side of the relation as "inverse". See last paragraph of section 6.4: http://nhibernate.info/doc/nh/en/index.html#collections-onetomany
I have many-to-one mappings working fine, but a one-to-many relationship between locations and location_times keeps giving me an error.
I keep getting this error:
on this line of code:
Mappings look like this:
Location:
public virtual IList<LocationTimes> LocationTimes { get; set; }
public virtual int locationID { get; set; }
public virtual IList<LocationTimes> LocationTimes { get; set; }
public Location()
{
LocationTimes = new List<LocationTimes>();
}
Location Map:
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Table("Locations");
Id(x => x.locationID).Column("ID");
HasMany(x => x.LocationTimes)
.Inverse()
.Cascade.All();
Location Table:
CREATE TABLE [dbo].[Locations](
[ID] [int] IDENTITY(1,1) NOT NULL
...
CONSTRAINT [PK_Locations_1] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
LocationTimes:
public class LocationTimes
{
public virtual int ID { get; set; }
public virtual Location Location { get; set; }
}
LocationTimesMap:
public class LocationTimesMap : ClassMap<LocationTimes>
{
public LocationTimesMap()
{
Table("Location_Times");
Id(x => x.ID);
References(x => x.Location).Column("LID");
}
}
Location_times table:
CREATE TABLE [dbo].[Location_Times](
[ID] [int] IDENTITY(1,1) NOT NULL,
[LID] [int] NULL,
[EHStart] [int] NULL,
[EHEnd] [int] NULL,
[EHSell] [money] NULL,
CONSTRAINT [PK_Location_Times_1] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
The full error message:
"{"could not initialize a collection: [WhygoDomain.Location.LocationTimes#4]
[SQL: SELECT locationti0_.Location_id as Location4_1_, locationti0_.ID as ID1_, locationti0_.ID as ID1_0_, locationti0_.LID as LID1_0_, locationti0_.EHStart as EHStart1_0_
FROM Location_Times locationti0_
WHERE locationti0_.Location_id=?]"}"
I can see from the sql in the error message that it is indeed looking for locationti0_.Location_id, which I know doesn't exist. I don't know why it's looking for that though.
This is usually a problem of ID name mis-matches on your tables. Check to ensure that Location has an ID column on the table and that it follows your convention or is mapped correctly. You don't share Location's map, full object graph, or any of the tables so its hard to tell what the IDs are named and if they are matching up correctly.
Edit:
Per RichardD's answer in the comments, modify the LocationMap to be as follows:
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Table("Locations");
Id(x => x.locationID).Column("ID");
HasMany(x => x.LocationTimes).KeyColumn("LID").Inverse().Cascade.All();