Fluent NHibernate one-to-one with bidirectional HasOne not saving child - fluent-nhibernate

I'm having a problem getting a one-to-one relationship working with fluent nhibernate. I've read a multitude of posts on this and it seems like the preferred approach is the shared primary key method, where the parent and child have the same primary key value and child PK is also FK to parent.
The parent and child classes each have a reference to the other (bi-directional HasOne) and the child is configured to get it's key value from the parent.
I believe I have it setup correctly, but when I create a new Parent, attach a new Child and then try to save it, the parent saves correctly and the key value is updated on the child object, but the child is not actually saved to the DB.
Here are the classes and maps:
public class Parent
{
public virtual int ParentID { get; set; }
public virtual string Name { get; set; }
public virtual Child Child { get; set; }
public virtual void Add(Child child)
{
child.Parent = this;
Child = child;
}
}
public class Child
{
public virtual int ParentID { get; set; }
public virtual string Name { get; set; }
public virtual Parent Parent { get; set; }
}
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.ParentID).GeneratedBy.Identity();
Map(x => x.Name);
HasOne(x => x.Child).Cascade.All();
}
}
public class ChildMap : ClassMap<Child>
{
public ChildMap()
{
Id(x => x.ParentID).GeneratedBy.Foreign("Parent");
Map(x => x.Name);
HasOne(x => x.Parent).Constrained().ForeignKey();
}
}
Here is the schema that NHibernate creates (removed some GOs for brevity):
CREATE TABLE [dbo].[Parent](
[ParentID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](255) NULL,
PRIMARY KEY CLUSTERED
(
[ParentID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[Child](
[ParentID] [int] NOT NULL,
[Name] [nvarchar](255) NULL,
PRIMARY KEY CLUSTERED
(
[ParentID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[Child] WITH CHECK ADD CONSTRAINT [FK_ChildToParent] FOREIGN KEY([ParentID])
REFERENCES [dbo].[Parent] ([ParentID])
ALTER TABLE [dbo].[Child] CHECK CONSTRAINT [FK_ChildToParent]
So it seems to create the schema I expected. But I just can't get the child to save:
var parent = new Parent() { Name = "Parent_1" };
parent.Add(new Child { Name = "Child_1" });
session.Save(parent);
As mentioned, this will save the parent correctly and update the ParentID property on the Child object with the value from the parent, but the Child is not actually saved to the DB and no error is generated.
Any idea what I'm doing wrong?

Never mind. I feel stupid. The mappings are correct, but you have to wrap the Save in a transaction for the child object to be persisted, which I wasn't doing in my test app. Once I added the transaction it worked as expected.

Related

.Net Core EF doesn't allow me to enter same Foreign Key

I have got a Model with Foreign Key. Some how .NET Core EF doesn't let me enter the same Foreign key twice to the table.
public class DolsMcaClientModel
{
[Key]
public int DolsMcaClientID { get; set; }
public int DolsMcaItemID { get; set; }
public virtual DolsMcaItemModel DolsMcaItemModel { get; set; }
public int ClientID { get; set; }
[Required]
public int FileID { get; set; }
public virtual FileModel FileModel { get; set; }
}
In this table DolsMcaClientID is my Identity Column and DolsMcaItemID is my Foreign column. DolsMcaItemModel is my navigation property.
I can't enter the same DolsMcaItemID for different client.
DolsMcaItemID acting like a primary key in the table. But it is not.
ERROR: System.Data.SqlClient.SqlException: Cannot insert duplicate key row in object 'dbo.DolsMcaClientModels' with unique index 'IX_DolsMcaClientModels_DolsMcaItemID'. The duplicate key value is (1).
If I delete DolsMcaItemID 1 item from the table, then i can add new entry to the table with DolsMcaItemId 1, but only once with any clientID... i can't enter DolsMcaItem 1 for with any other clientID
My SQL Table create query is:
CREATE TABLE [dbo].[DolsMcaClientModels](
[DolsMcaClientID] [int] IDENTITY(1,1) NOT NULL,
[DolsMcaItemID] [int] NOT NULL,
[ClientID] [int] NOT NULL,
[FileID] [int] NOT NULL,
CONSTRAINT [PK_DolsMcaClientModels] PRIMARY KEY CLUSTERED
(
[DolsMcaClientID] 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].[DolsMcaClientModels] WITH CHECK ADD CONSTRAINT [FK_DolsMcaClientModels_DolsMcaItemModels_DolsMcaItemID] FOREIGN KEY([DolsMcaItemID])
REFERENCES [dbo].[DolsMcaItemModels] ([DolsMcaItemID])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[DolsMcaClientModels] CHECK CONSTRAINT [FK_DolsMcaClientModels_DolsMcaItemModels_DolsMcaItemID]
GO
ALTER TABLE [dbo].[DolsMcaClientModels] WITH CHECK ADD CONSTRAINT [FK_DolsMcaClientModels_FileModels_FileID] FOREIGN KEY([FileID])
REFERENCES [dbo].[FileModels] ([FileID])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[DolsMcaClientModels] CHECK CONSTRAINT [FK_DolsMcaClientModels_FileModels_FileID]
GO
The UNIQUE index [IX_DolsMcaClientModels_DolsMcaItemID] on that column is preventing you from committing another record with the same [DolsMcaItemID].
Fix is to drop the unique index:
ALTER TABLE [dbo].[DolsMcaClientModels] DROP INDEX [IX_DolsMcaClientModels_DolsMcaItemID];
CREATE INDEX [IX_DolsMcaClientModels_DolsMcaItemID] ON [dbo].[DolsMcaClientModels] ([DolsMcaItemID])
Don't forget to scaffold your db context to remove the constraint from code.

Have an Entity Framework field reference one of several derived tables via a discriminator (Using TPT)

I'm trying to find a way to create this relationship from Entity Framework 5's Code First.
In the example below, I have some Stock which can either belong to a Warehouse or a Row.
Stock Table
'Id', 'SerialNo', 'Discriminator', 'LocationId'
345, ABC123, Warehouse, 4
123, ABC124, Row, 12
Warehouse Table
'Id', 'Name'
4, WH-One
8, WH-Two
Row Table
'Id', 'Name'
6, RowA
12, RowB
The Discriminator column on the table Stock determines which of the related tables the LocationId is referring to. For example:
Stock with Id '345' has a reference to row with Id: 4 (WH-One) on the Warehouse table
Can this type of relationship be mapped with EF Code First? It's certainly no trouble to query against this structure in SQL Server.
SELECT Stock.SerialNo, COALESCE(Warehouse.Name, Row.Name) AS Name
FROM Stock
LEFT OUTER JOIN Warehouse ON Stock.Id = Warehouse.Id AND Stock.Discriminator = 'Warehouse'
LEFT OUTER JOIN Row ON Stock.Id = Row.Id AND Stock.Discriminator = 'Row'
Which should return:
SerialNo, Name
ABC123, WH-One
ABC124, RowB
Ideally, I'd like to have either an interface or abstract base class which Warehouse and Row and derive from. And a navigation property called Location that would refer to either table cast to the abstract/interface.
Alternatively, I'd like to know if anyone has a better suggestion to describe a relationship in EF Code First that allows one entity to reference one of several other similar entities.
It's Code First so I would do this:
First - create the classes I want in code:
public abstract class Location
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Stock> StockItems { get; set; }
}
public class Warehouse : Location
{
public int NumberOfLoadingDocks { get; set; }
}
public class Row : Location
{
public int NumberOfShelves { get; set; }
}
public class Stock
{
public int ID { get; set; }
public string SerialNo { get; set; }
public int LocationID { get; set; }
}
Then add them to the context:
public partial class MyContext : DbContext
{
public DbSet<Location> Locations { get; set; }
public DbSet<Warehouse> Warehouses { get; set; }
public DbSet<Row> Rows { get; set; }
public DbSet<Stock> Stocks { get; set; }
}
Then in Package-Manager console:
Add-Migration "Locations"
Update-Database -Script
That lets me see the tables Entity Framework would create:
CREATE TABLE [dbo].[Stocks] (
[ID] [int] NOT NULL IDENTITY,
[SerialNo] [nvarchar](max),
[LocationID] [int] NOT NULL,
CONSTRAINT [PK_dbo.Stocks] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_LocationID] ON [dbo].[Stocks]([LocationID])
CREATE TABLE [dbo].[Locations] (
[ID] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
[NumberOfShelves] [int],
[NumberOfLoadingDocks] [int],
[Discriminator] [nvarchar](128) NOT NULL,
CONSTRAINT [PK_dbo.Locations] PRIMARY KEY ([ID])
)
ALTER TABLE [dbo].[Stocks] ADD CONSTRAINT [FK_dbo.Stocks_dbo.Locations_LocationID] FOREIGN KEY ([LocationID]) REFERENCES [dbo].[Locations] ([ID])
INSERT [dbo].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
VALUES (N'201309120832257_Locations'....
Not the same tables that you have. This is Table-per-Hierarchy inheritance. If you are not happy with that then you need to look at Table Per Type inheritance
EDIT
To get TPT inheritance you can add the Table attribute to the inherited classes like this:
[System.ComponentModel.DataAnnotations.Schema.Table("Warehouses")]
public class Warehouse : Location
{
public int NumberOfLoadingDocks { get; set; }
}
[System.ComponentModel.DataAnnotations.Schema.Table("Rows")]
public class Row : Location
{
public int NumberOfShelves { get; set; }
}
And now you get separate tables for each type:
CREATE TABLE [dbo].[Locations] (
[ID] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
CONSTRAINT [PK_dbo.Locations] PRIMARY KEY ([ID])
)
CREATE TABLE [dbo].[Rows] (
[ID] [int] NOT NULL,
[NumberOfShelves] [int] NOT NULL,
CONSTRAINT [PK_dbo.Rows] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_ID] ON [dbo].[Rows]([ID])
CREATE TABLE [dbo].[Warehouses] (
[ID] [int] NOT NULL,
[NumberOfLoadingDocks] [int] NOT NULL,
CONSTRAINT [PK_dbo.Warehouses] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_ID] ON [dbo].[Warehouses]([ID])
ALTER TABLE [dbo].[Stocks] ADD CONSTRAINT [FK_dbo.Stocks_dbo.Locations_LocationID] FOREIGN KEY ([LocationID]) REFERENCES [dbo].[Locations] ([ID])
ALTER TABLE [dbo].[Rows] ADD CONSTRAINT [FK_dbo.Rows_dbo.Locations_ID] FOREIGN KEY ([ID]) REFERENCES [dbo].[Locations] ([ID])
ALTER TABLE [dbo].[Warehouses] ADD CONSTRAINT [FK_dbo.Warehouses_dbo.Locations_ID] FOREIGN KEY ([ID]) REFERENCES [dbo].[Locations] ([ID])
And if you are not happy with that you should look at Table Per Concrete Type Inheritance...

Batch Insert - Foreign Key Not Working

I'm trying to do a batch insert and it's not working. I thought I had this working but something seems to have broken and I'd appreciate it if someone could show me what.
Edit - Here's the database schema:
CREATE TABLE [dbo].[Categories](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
CONSTRAINT [PK_Categories] PRIMARY KEY CLUSTERED ([Id])
)
CREATE TABLE [dbo].[ProductTopSellersCategory](
[ProductId] [int] NOT NULL,
[CategoryId] [int] NOT NULL,
[Order] [int] NOT NULL,
CONSTRAINT [PK_ProductTopSellersCategory]
PRIMARY KEY CLUSTERED ([ProductId], [CategoryId])
)
ALTER TABLE [dbo].[ProductTopSellersCategory] ADD
CONSTRAINT [FK_ProductTopSellersCategory_Products]
FOREIGN KEY ([ProductId]) REFERENCES [dbo].[Products] ([Id]),
CONSTRAINT [FK_ProductTopSellersCategory_Categories]
FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[Categories] ([Id])
I have the following entities:
public class Category {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class ProductTopSellerCategory {
public virtual ProductTopSellerCategoryIdentifier Id { get; set; }
private Product _product;
public virtual Product Product {
get { return _product; }
set { _product = value; Id.ProductId = _product.Id; }
}
private Category _category;
public virtual Category Category {
get { return _category; }
set { _category = value; Id.CategoryId = _category.Id; }
}
[Required]
public virtual int Order { get; set; }
public ProductTopSellerCategory() {
Id = new ProductTopSellerCategoryIdentifier();
}
}
[Serializable]
public class ProductTopSellerCategoryIdentifier {
public virtual int ProductId { get; set; }
public virtual int CategoryId { get; set; }
#region Composite Id Members
public override bool Equals(object obj) {
if (obj == null || !(obj is ProductTopSellerCategoryIdentifier))
return false;
var i = (ProductTopSellerCategoryIdentifier)obj;
return ProductId == i.ProductId && CategoryId == i.CategoryId;
}
public override int GetHashCode() {
return ToString().GetHashCode();
}
public override string ToString() {
return ProductId + "|" + CategoryId;
}
#endregion
}
With the corresponding fluent mappings:
public class CategoryMap : ClassMap<Category> {
public CategoryMap() {
Table("Categories");
Id(x => x.Id);
Map(x => x.Name);
}
}
public class ProductTopSellerCategoryMap : ClassMap<ProductTopSellerCategory> {
public ProductTopSellerCategoryMap() {
Table("ProductTopSellersCategory");
CompositeId(x => x.Id)
.KeyProperty(x => x.ProductId)
.KeyProperty(x => x.CategoryId);
References(x => x.Product).ReadOnly();
References(x => x.Category).ReadOnly();
Map(x => x.Order, "[Order]");
}
}
Now when I say:
var category = new Category() { Name = "Test 1" };
var product = session.Get<Product>(1);
var topSeller = new ProductTopSellerCategory() { Product = product, Category = category };
session.SaveOrUpdate(category);
session.SaveOrUpdate(topSeller);
session.Transaction.Commit();
It throws the error:
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_ProductTopSellersCategory_Categories". The conflict occurred in
database "xxx", table "dbo.Categories", column 'Id'. The statement has
been terminated.
I've tried to simplify this example as much as possible. I'd really appreciate the help. Thanks
You have a one-to-many relationship between Category and ProductTopSellerCategory with just the many side mapped. Normally you would use the inverse attribute on the collection mapped on the one side but you don't have that mapped so I suggest:
using (var txn = session.BeginTransaction())
{
var category = new Category() { Name = "Test 1" };
session.Save(category);
session.Flush();
var product = session.Get<Product>(1);
var productTopSellerCategory = new ProductTopSellerCategory() { Product = product, Category = category };
session.Save(productTopSellerCategory);
txn.Commit();
}
The problem with your original code is that NHibernate is attempting to insert the new ProductTopSellerCategory then update the category. It's doing this because the inverse attribute is not set. Forcing NHibernate to insert the new Category by flushing the session should resolve the problem.
I think I've found a solution. It's a little bit of a hack but it meant I didn't have to change my entities and mappings. The issue happens because the CategoryId in the identity type doesn't point to the same reference as the Category.Id in the top sellers entity. To fix this issue I need to add the following just before I insert the top seller:
topSeller.Id.CategoryId = topSeller.Category.Id;

nhibernate connecting to legacy database

I am connecting to a legacy sqlserver database. One of the tables has column name "Primary". The scripts are failing due to that.
Script generated by nhibernate:
SELECT locations0_.CustomerID as CustomerID1_, locations0_.LocationID as LocationID1_, locations0_.LocationID as LocationID2_0_, locations0_.Primary as Primary2_0_,locations0_.CustomerID as CustomerID2_0_
FROM dbo.tblLocation locations0_
WHERE locations0_.CustomerID=?
Class:
public class Location
{
public virtual int LocationID { get; set; }
public virtual Customer Customer { get; set; }
public virtual int? CustomerID { get; set; }
public virtual string LocationName { get; set; }
public virtual string Address1 { get; set; }
public virtual string Address2 { get; set; }
public virtual string Address3 { get; set; }
public virtual string City { get; set; }
public virtual string StateOrProvince { get; set; }
public virtual string PostalCode { get; set; }
public virtual datetime? LTimeStamp{ get;set; }
public virtual bool Primary { get; set; }
}
Map:
public class TblLocationMap : ClassMap
{
public TblLocationMap()
{
Table("tblLocation");
//LazyLoad();
Id(x => x.LocationID).GeneratedBy.Identity().Column("LocationID");
References(x => x.Customer).Column("CustomerID");
Map(x => x.LocationName).Column("LocationName").Length(50);
Map(x => x.Address1).Column("Address1").Length(200);
Map(x => x.Address2).Column("Address2").Length(200);
Map(x => x.Address3).Column("Address3").Length(200);
Map(x => x.City).Column("City").Length(100);
Map(x => x.StateOrProvince).Column("StateOrProvince").Length(100);
Map(x => x.PostalCode).Column("PostalCode").Length(20);
//Map(x => x.Primary).Column("Primary").Not.Nullable();
//Map(x => x.LTimestamp).Column("LTimestamp");
HasMany(x => x.Contacts).KeyColumn("LocationID");
}
sql:
CREATE TABLE [dbo].[tblLocation]
(
[LocationID] [int] IDENTITY(1,1) NOT NULL,
[CustomerID] [int] NULL,
[LocationName] nvarchar NULL,
[Address1] nvarchar NULL,
[Address2] nvarchar NULL,
[Address3] nvarchar NULL,
[City] nvarchar NULL,
[StateOrProvince] nvarchar NULL,
[PostalCode] nvarchar NULL,
[Primary] [bit] NOT NULL,
[RecTimestamp] [timestamp] NULL,
(
[LocationID] 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
GenericADOException:
could not initialize a collection: [Domain.Locations#466][SQL: SELECT locations0_.CustomerID as CustomerID1_, locations0_.LocationID as LocationID1_, locations0_.LocationID as LocationID2_0_, locations0_.LocationName as Location2_2_0_, locations0_.Address1 as Address3_2_0_, locations0_.Address2 as Address4_2_0_, locations0_.Address3 as Address5_2_0_, locations0_.City as City2_0_, locations0_.StateOrProvince as StateOrP7_2_0_, locations0_.PostalCode as PostalCode2_0_, locations0_.Primary as Primary2_0_, locations0_.CustomerID as CustomerID2_0_ FROM dbo.tblLocation locations0_ WHERE locations0_.CustomerID=?]
Inner exception:
{"Incorrect syntax near the keyword 'Primary'."}
I suspect Primary is a reserved word and is not getting escaped properly, therefore try implicitly escaping the column name with back ticks....
e.g.
Map(x => x.Primary).Column("`Primary`").Not.Nullable();
NHibernate will automatically swap your backtick with square braces if you are using MsSql server [Primary]
Strange that the schema is generating square braces but the select SQL isn't.

Mapping self-referencing IDictionary<string, Entity> with Fluent NHibernate

I have the following entities in my domain model:
class Entity
{
public int Id { get; set; }
}
class Foo : Entity
{
public IDictionary<string, Attribute> Attributes { get; set; }
}
class Bar : Entity
{
public IDictionary<string, Attribute> Attributes { get; set; }
}
class Attribute : Entity
{
public string Key { get; set; }
public string Value { get; set; }
public IDictionary<string, Attribute> Attributes { get; set; }
}
I'd like to map these dictionaries with Fluent NHibernate. I've gotten most things to work, but first I'm having difficulties with the self-referencing Attribute.Attributes property. This is due to NHibernate making the Key a primary key of the Attribute table as well as the Id it inherits from Entity. This is how my mapping works:
ManyToManyPart<Attribute> manyToMany = mapping
.HasManyToMany<Attribute>(x => x.Attributes)
.ChildKeyColumn("AttributeId")
.ParentKeyColumn(String.Concat(entityName, "Id"))
.AsMap(x => x.Key, a => a.Column("`Key`"))
.Cascade.AllDeleteOrphan();
if (entityType == typeof(Attribute))
{
manyToMany
.Table("AttributeAttribute")
.ParentKeyColumn("ParentAttributeId");
}
If I replace the if statement with the following:
if (entityType == typeof(Attribute))
{
manyToMany
.Table("Attribute")
.ParentKeyColumn("ParentAttributeId");
}
I get the following exception:
NHibernate.FKUnmatchingColumnsException : Foreign key (FK_Attribute_Attribute [ParentAttributeId])) must have same number of columns as the referenced primary key (Attribute [ParentAttributeId, Key])
This is due to NHibernate automatically making Key the primary key alongside Id in my Attribute column. I'd like Key to not be primary key, since it shows up in all of my many to many tables;
create table FooAttribute (
FooId INT not null,
AttributeId INT not null,
[Key] NVARCHAR(255) not null
)
I'd like the foreign keys to only reference Id and not (Id, Key), since having Key as a primary key requires it to be unique, which it won't be across all of my ManyToManys.
where do you map Attribute itself (does it contain a Composite Key)?
AttributeValue may be a better name to show that it contains a value.
.AsMap(x => x.Key) is enough to say that Key should be the dictionary key
create table FooAttribute (
FooId INT not null,
AttributeValueId INT not null
)
or consider using
mapping.HasMany(x => x.Attributes)
.KeyColumn(entity + Id)
.AsMap(x => x.Key)
.Cascade.AllDeleteOrphan();
which will create
create table Attribute (
Id INT not null,
FooId INT,
BarId INT,
ParentAttributeId INT,
Key TEXT,
Value TEXT,
)