I'm having troubles figuring out why I can save directly to the collection with ontext.CommunicationPreferences.Add. But Trying to add to guest.communicationPreference does not work.
Am I missing some mapping somewhere? Thanks!
Code
[TestMethod]
public void TestMethod1()
{
var guests = new Context().Guests.ToList();
var communicationTypes = new Context().CommunicationTypes.ToList();
var communicationPreferences = new Context().CommunicationPreferences.ToList();
using (var context = new Context())
{
var guest = guests.FirstOrDefault(x => x.Id == 1);
// This works
context.CommunicationPreferences.Add(new CommunicationPreference(1, guest.Id, communicationTypes.First().Id));
// This does not work - why? :(
guest.CommunicationPreference = new CommunicationPreference(1, guest.Id, communicationTypes.First().Id);
context.SaveChanges();
}
}
public class CommunicationPreference
{
public CommunicationPreference()
{
}
public CommunicationPreference(int time, int guestId, int communicationTypeId)
{
Time = time;
GuestId = guestId;
CommunicationTypeId = communicationTypeId;
}
public int GuestId { get; set; }
public int? CommunicationTypeId { get; set; }
public virtual CommunicationType CommunicationType { get; set; }
public virtual Guest Guest { get; set; }
public int? Time { get; set; }
}
public class CommunicationType
{
public CommunicationType()
{
}
public int Id { get; set; }
public string Name { get; set; }
}
public class Guest
{
public Guest()
{
}
public int Id { get; set; }
public string FirstName { get; set; }
public virtual CommunicationPreference CommunicationPreference { get; set; }
}
public class Context : DbContext
{
public Context() : base("Context")
{
}
public DbSet<Models.Guest> Guests { get; set; }
public DbSet<Models.CommunicationType> CommunicationTypes { get; set; }
public DbSet<Models.CommunicationPreference> CommunicationPreferences { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
modelBuilder.Entity<Models.CommunicationPreference>().HasKey(t => t.GuestId);
modelBuilder
.Entity<Models.CommunicationPreference>()
.HasRequired<Models.Guest>(x => x.Guest)
.WithOptional(x => x.CommunicationPreference);
}
}
Database
CREATE TABLE [dbo].[Guest](
[Id] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
CONSTRAINT [PK_Guest] 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
CREATE TABLE [dbo].[CommunicationType](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NOT NULL,
CONSTRAINT [PK_CommunicationType] 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
CREATE TABLE [dbo].[CommunicationPreference](
[Time] [int] NULL,
[CommunicationTypeId] [int] NULL,
[GuestId] [int] NOT NULL,
CONSTRAINT [PK_CommunicationPreference] PRIMARY KEY CLUSTERED
(
[GuestId] 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].[CommunicationPreference] WITH CHECK ADD CONSTRAINT [FK_CommunicationPreference_CommunicationType] FOREIGN KEY([CommunicationTypeId])
REFERENCES [dbo].[CommunicationType] ([Id])
GO
ALTER TABLE [dbo].[CommunicationPreference] CHECK CONSTRAINT [FK_CommunicationPreference_CommunicationType]
GO
ALTER TABLE [dbo].[CommunicationPreference] WITH CHECK ADD CONSTRAINT [FK_CommunicationPreference_Guest] FOREIGN KEY([GuestId])
REFERENCES [dbo].[Guest] ([Id])
GO
ALTER TABLE [dbo].[CommunicationPreference] CHECK CONSTRAINT [FK_CommunicationPreference_Guest]
GO
You are loading your data in 4 different context instances....
This:
var guests = new Context().Guests.ToList(); // Context 1
var communicationTypes = new Context().CommunicationTypes.ToList(); // Context 2
var communicationPreferences = new Context().CommunicationPreferences.ToList(); // Context 3
using (var context = new Context()) // Context 4
{
var guest = guests.FirstOrDefault(x => x.Id == 1);
// This works
context.CommunicationPreferences.Add(new CommunicationPreference(1, guest.Id, communicationTypes.First().Id));
// This does not work - why? :(
guest.CommunicationPreference = new CommunicationPreference(1, guest.Id, communicationTypes.First().Id);
context.SaveChanges();
}
Put everything in the same context because Context #4 (using one) doesn't know about entities from Context 1 (guests)
This would work:
using (var context = new Context())
{
var guests = context.Guests.ToList();
var communicationTypes = context.CommunicationTypes.ToList();
var communicationPreferences = context.CommunicationPreferences.ToList();
var guest = guests.FirstOrDefault(x => x.Id == 1);
guest.CommunicationPreference = new CommunicationPreference(1, guest.Id, communicationTypes.First().Id);
context.SaveChanges();
}
I am ignoring every other aspects except why not updating.
When you adding entity by add() it set state of the that entity to modified automatically but if you not using add() then
You need to modify your state of entity manually.
context.Entry(guest.CommunicationPreference).State = EntityState.Modified;
You need to add above line before saving changes.
in this block of code
context.CommunicationPreferences.Add(new CommunicationPreference(1, guest.Id, communicationTypes.First().Id));
// This does not work - why? :(
guest.CommunicationPreference = new CommunicationPreference(1, guest.Id, communicationTypes.First().Id);
context.SaveChanges();
The "guest" is not relate or not in work context of your "Context" instance so guest never save. I hope you get it.
Related
I have the following two tables
CREATE TABLE [Finance].[Account]
(
[AccountId] INT IDENTITY(1, 1) NOT NULL,
[Name] NVARCHAR(100) NOT NULL,
[AvailableBalance] DECIMAL(13, 4) NULL,
[CurrentBalance] DECIMAL(13, 4) NOT NULL,
[TypeId] INT NULL,
CONSTRAINT [PK_Account_Id]
PRIMARY KEY CLUSTERED ([AccountId] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
CONSTRAINT [FK_Account_Type]
FOREIGN KEY (TypeId) REFERENCES [Finance].[AccountType](AccountTypeId)
) ON [PRIMARY]
CREATE TABLE [Finance].[AccountType]
(
[AccountTypeId] INT IDENTITY(1, 1) NOT NULL,
[AccountTypeParentId] INT NULL,
[Name] NVARCHAR(100) NOT NULL
CONSTRAINT [PK_AccountType]
PRIMARY KEY CLUSTERED ([AccountTypeId])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
CONSTRAINT [FK_AccountType_Parent_AccountType]
FOREIGN KEY (AccountTypeParentId) REFERENCES [Finance].[AccountType](AccountTypeId),
) ON [PRIMARY]
With the following class structure
public class Account
{
public int AccountId { get; private set; }
public string Name { get; private set; }
public decimal AvailableBalance { get; private set; }
public decimal CurrentBalance { get; private set; }
public AccountType Type { get; private set; }
public void SetType(AccountType type)
{
if (type != null)
{
Type = type;
}
}
}
public class AccountType
{
public int AccountTypeId { get; private set; }
public string Name { get; private set; }
public int? AccountTypeParentId { get; private set; } = null;
public AccountType ParentType { get; private set; }
protected AccountType()
{
}
private AccountType(int accountTypeId, string name)
{
AccountTypeId = accountTypeId;
Name = name;
}
public static AccountType CreateAccountType(string name)
{
return new AccountType(0, name, DateTime.UtcNow);
}
public void SetParent(AccountType type)
{
ParentType = type;
}
}
I am using following mapping in EF Core 6
modelBuilder.Entity<AccountType>(entity =>
{
entity.ToTable("AccountType", schema: "Finance").HasKey(c=>c.AccountTypeId);
entity.Property(c => c.AccountTypeId).ValueGeneratedOnAdd();
entity.Property(t => t.AccountTypeId).HasColumnName("AccountTypeId");
entity.Property(t => t.Name).HasColumnName("Name");
entity.Property(t => t.AccountTypeParentId).HasColumnName("AccountTypeParentId");
entity.HasOne(t => t.ParentType)
.WithMany()
.HasForeignKey("AccountTypeParentId");
});
modelBuilder.Entity<Account>(entity =>
{
entity.ToTable("Account", schema: "Finance");
entity.Property(c => c.AccountId).ValueGeneratedOnAdd();
entity.Property(o => o.CurrentBalance).HasColumnType("decimal(13,4)");
entity.Property(o => o.AvailableBalance).HasColumnType("decimal(13,4)");
entity.HasOne(b => b.Type).WithOne().HasForeignKey<AccountType>(b => b.AccountTypeId);
});
When trying to insert a record using the following call
Account account = CreateAccount();
AccountType type = AccountType.CreateAccountType("ParentType");
type.SetParent(AccountType.CreateAccountType("ChildType"));
account.SetType(type);
_context.Accounts.Add(account);
I am getting the following exception
SqlException: Cannot insert explicit value for identity column in table 'AccountType' when IDENTITY_INSERT is set to OFF.
It seems like everything is set right, I am new to EF Core, so not sure how it handles recursive insert.
Thanks in advance for any help.
I have the following Classes
public class Children {
public Children (){} // constructor
public int Id {get;set;},
public string ChildName {get; set;}
}
public class Parents {
public Parents(){} // constructor
public int Id {get;set;},
public string ParentName {get;set;}
public Children Child {get; set;}
}
I want 1 child per parent, In the configuration I have
public void Configure(EntityTypeBuilder<Parents> configuration)
{
configuration.ToTable("Parents");
configuration.Property(p => p.Id).HasField("_id");
configuration.HasOne(a => a.ChildClass).WithMany().OnDelete(DeleteBehavior.Cascade);
}
public void Configure(EntityTypeBuilder<Children> configuration)
{
configuration.ToTable("Children");
configuration.Property(p => p.Id).HasField("_id");
}
When I use the entity framework 5 builder I get 2 tables back from the database
CREATE TABLE [dbo].[Parents](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ChildId] [int] NOT NULL,
[ParentName] [nvarchar](max) NULL,
CONSTRAINT [PK_Parents] 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]
ALTER TABLE [dbo].[Parents] WITH CHECK ADD CONSTRAINT
[FK_Parents_Children_Child] FOREIGN KEY([ChildId])
REFERENCES [dbo].[Children] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Parents] CHECK CONSTRAINT [FK_Parents_Parents_Children_ChildId]
GO
CREATE TABLE [dbo].[Children](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ChildName] [nvarchar](max) NULL,
[ParentId] [int] NULL,
CONSTRAINT [PK_Children] 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]
GO
ALTER TABLE [dbo].[Children] WITH CHECK ADD CONSTRAINT [FK_Children_Parent_ParentId] FOREIGN
KEY([ParentId])
REFERENCES [dbo].[Parents] ([Id])
GO
ALTER TABLE [dbo].[Children] CHECK CONSTRAINT [FK_Children_Parent_ParentId]
GO
Basically I get stuck in a loop as I cannot insert a Parents record, and I cannot insert a son as they need both sets of Id's, I dont know why the Children table needs a reference to the Parents table, if the parents table already references the Children table.
Based on your fluent mapping, it looks like you've set up a one-to-many relationship, where each child can have many parents. But you actually want a one-to-one relationship.
public class Children {
public Children (){}
public int Id { get;set; }
public string ChildName { get; set; }
public int ParentId { get; set; }
public Parents Parent { get; set; }
}
public class Parents {
public Parents(){}
public int Id { get; set; }
public string ParentName { get; set; }
public Children Child { get; set; }
}
public void Configure(EntityTypeBuilder<Parents> configuration)
{
configuration.ToTable("Parents");
configuration.Property(p => p.Id).HasField("_id");
configuration.HasOne(p => p.Child).WithOne(c => c.Parent).HasForeignKey<Children>(c => c.ParentId).OnDelete(DeleteBehavior.Cascade);
}
However, the generated SQL doesn't seem to match your model nor your fluent configuration.
I have an entity setup as follows
public class ExampleObject
{
[Key]
public int ID { get; set; }
public int RelationID { get; set; }
public string SomeValue { get; set; }
[ForeignKey("RelationID")]
public virtual RelationObj RelationObj { get; set; }
}
Later in my project I use this entity as follows
foreach (var rec in allRecs)
{
db.ExampleObject.Attach(rec);
rec.SomeValue = "TEST"
}
db.SaveChanges();
This throws a DbEntityValidationException "The field "RelationObj" is required"
I don't want to include the RelationObj when loading these records, that affects performance. Why is EF bothering to check the foreign relation object? How do I go about fixing this?
First create the db, change the database on top of the script to match your db
USE [Breaz]
GO
/****** Object: Table [dbo].[Relation] Script Date: 6/29/2016 4:35:32 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Relation](
[ID] [int] IDENTITY(1,1) NOT NULL,
[RelatedSomeValue] [varchar](10) NULL,
CONSTRAINT [PK_Relation] 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
SET ANSI_PADDING OFF
GO
Then create the next table, again change the db (Breaz):
USE [Breaz]
GO
/****** Object: Table [dbo].[Example] Script Date: 6/29/2016 4:34:15 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Example](
[ID] [int] IDENTITY(1,1) NOT NULL,
[RelationID] [int] NOT NULL,
[SomeValue] [varchar](10) NULL,
CONSTRAINT [PK_Example] 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
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Example] WITH CHECK ADD CONSTRAINT [FK_Example_Relation] FOREIGN KEY([RelationID])
REFERENCES [dbo].[Relation] ([ID])
GO
ALTER TABLE [dbo].[Example] CHECK CONSTRAINT [FK_Example_Relation]
GO
Add a couple of rows of data into each table.
Add new item ADO.NET Entity Data Model, from your db and add both tables. Name the edmx ExampleModel. Get the (BreazEntities7) context (your context) from the Example.Context.cs file.
public class HomeController : Controller
{
public ActionResult YIndex()
{
using (BreazEntities7 db = new BreazEntities7())
{
var allRecs = db.Examples;
foreach (var rec in allRecs)
{
rec.SomeValue = "TEST";
}
db.SaveChanges();
}
return View();
}
Here is the generated Example:
namespace Testy2.Models
{
using System;
using System.Collections.Generic;
public partial class Example
{
public int ID { get; set; }
public int RelationID { get; set; }
public string SomeValue { get; set; }
public virtual Relation Relation { get; set; }
}
}
Here id the generated Relation:
namespace Testy2.Models
{
using System;
using System.Collections.Generic;
public partial class Relation
{
public Relation()
{
this.Examples = new HashSet<Example>();
}
public int ID { get; set; }
public string RelatedSomeValue { get; set; }
public virtual ICollection<Example> Examples { get; set; }
}
}
I'm having a problem with ServiceStack OrmLite for SQL Server in a Visual Studio 2013 C# project. My problem is that I'm trying to use the SqlExpression builder and it's not capturing my table schema and the generated SQL code is not correct. When I run the code, I get a System.Data.SqlClient.SqlException that says "Invalid object name 'ReportPages'."
I'm using the latest NuGet version of ServiceStack.OrmLite, which is version 4.0.24.
Let me start with the table setup. Note that I removed the foreign keys for convenience:
-- Create the report pages table.
CREATE TABLE [MicroSite].[ReportPages](
[ReportPageID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](200) NULL,
[Template] [nvarchar](50) NULL,
[AccessLevel] [int] NOT NULL,
[AssignedEmployeeId] [int] NOT NULL,
[Disabled] [bit] NOT NULL,
[Deleted] [bit] NOT NULL,
[ReportSectionID] [int] NOT NULL,
[Index] [int] NOT NULL,
[Cover] [bit] NOT NULL,
CONSTRAINT [PK_dbo.ReportSections] PRIMARY KEY CLUSTERED
(
[ReportPageID] 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
-- Create the report sections table.
CREATE TABLE [MicroSite].[ReportSections](
[ReportSectionID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NULL,
[ReportID] [int] NOT NULL,
CONSTRAINT [PK_dbo.ReportSectionGroups] PRIMARY KEY CLUSTERED
(
[ReportSectionID] 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
-- Create the report editables table.
CREATE TABLE [MicroSite].[Editables](
[EditableID] [int] IDENTITY(1,1) NOT NULL,
[Index] [int] NOT NULL,
[Content] [nvarchar](max) NULL,
[Styles] [nvarchar](100) NULL,
[Type] [int] NOT NULL,
[ReportPageID] [int] NOT NULL,
CONSTRAINT [PK_dbo.Editables] PRIMARY KEY CLUSTERED
(
[EditableID] 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]
GO
So my tables basically look like this:
Here are my POCOs:
[Alias("ReportPages")]
[Schema("MicroSite")]
public partial class MicrositeReportPage : IHasId<int>
{
[Required]
public int AccessLevel { get; set; }
[Required]
public int AssignedEmployeeId { get; set; }
[Required]
public bool Cover { get; set; }
[Required]
public bool Deleted { get; set; }
[Required]
public bool Disabled { get; set; }
[Alias("ReportPageID")]
[AutoIncrement]
[PrimaryKey]
public int Id { get; set; }
[Required]
public int Index { get; set; }
public string Name { get; set; }
[Alias("ReportSectionID")]
[Required]
public int ReportSectionId { get; set; }
public string Template { get; set; }
}
[Alias("ReportSections")]
[Schema("MicroSite")]
public class MicrositeReportSection : IHasId<int>
{
[Alias("ReportSectionID")]
[AutoIncrement]
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
[Alias("ReportID")]
[Required]
public int ReportId { get; set; }
}
[Alias("Editables")]
[Schema("MicroSite")]
public partial class MicrositeEditable : IHasId<int>
{
public string Content { get; set; }
[Alias("EditableID")]
[AutoIncrement]
[PrimaryKey]
public int Id { get; set; }
[Required]
public int Index { get; set; }
[Alias("ReportPageID")]
[Required]
public int ReportPageId { get; set; }
public string Styles { get; set; }
[Alias("Type")]
[Required]
public int TypeId { get; set; }
}
The actual SQL statement I want to generate is this:
SELECT e.EditableID
, e.[Index]
, e.Content
, e.Styles
, e.Type
, e.ReportPageID
FROM
MicroSite.ReportSections AS rs
LEFT JOIN
MicroSite.ReportPages AS rp ON rp.ReportSectionID = rs.ReportSectionID AND rp.[Index] = 24
LEFT JOIN
MicroSite.Editables AS e ON e.ReportPageID = rp.ReportPageID
WHERE
rs.ReportID = 15
Here is my C# code:
var query = db.From<MicrositeReportSection>()
.LeftJoin<MicrositeReportSection, MicrositeReportPage>((section, page) => section.Id == page.ReportSectionId)
.LeftJoin<MicrositeReportPage, MicrositeEditable>((page, editable) => page.Id == editable.ReportPageId && page.Index == 24)
.Where<MicrositeReportSection>(section => section.ReportId == 15);
var sql = query.ToSelectStatement();
var result = db.Select<MicrositeEditable>(query)
The generated SQL statement from the sql variable looks like this (I formatted it for readability):
SELECT
"ReportSectionID",
"Name",
"ReportID"
FROM
"MicroSite"."ReportSections"
LEFT JOIN
"ReportPages" ON ("ReportSections"."ReportSectionID" = "ReportPages"."ReportSectionID")
LEFT JOIN "Editables"
ON (("ReportPages"."ReportPageID" = "Editables"."ReportPageID") AND ("ReportPages"."Index" = 24))
WHERE
("ReportSections"."ReportID" = 15)
First, the left joins are missing the schema name, which makes the SQL statement incorrect. Second, what's going on in the select statement? Those aren't the columns from the MicrositeEditable table. Am I doing this correctly or is this an actual bug with OrmLite?
I submitted a ticket with the development team and this is now fixed in version 4.0.25.
{"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