Nhibernate creating object which has an object property - c#

I'm just starting to work with NHibernate. I have two objects:
public class Supplier
{
public virtual int id{get;set;}
public virtual SupplierAddress address{get;set;}
public virtual string Name{get;set;}
}
public class SupplierAddress
{
public virtual int id{get;set;}
public virtual Supplier{get;set;}
public virtual string SupplierAddressLine{get;set;}
}
When I want to create a new Supplier I create a new object:
var supplierAddress = new SupplierAddress {
SupplierAddressLine = "someLine"
}
var supplier = new Supplier
{
Name = "someName",
SupplierAddress = SupplierAddressLine
}
Then, when i try to save using:
_session.Save(supplier);
I get the error: "Cannot insert the value NULL into column 'id'
Update 1 Mappings
for SupplierAddress
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
References(x => x.Supplier).Column("SupplierId");
Map(x => x.AddressLine1).Column("AddressLine1").Not.Nullable().Length(255);
for Supplier
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
References(x => x.SupplierAddress).Column("SupplierAddressId").Not.Nullable();
HasMany(x => x.SupplierAddresses).KeyColumn("SupplierId");

You should set some cascade rules on the Supplier → SupplierAddress relationship:
References(s => s.SupplierAddress)
.Column("SupplierAddressId")
.Not.Nullable()
.Cascade.All(); /* Cascade operations that happen on `Supplier` */
Otherwise, NHibernate does not know that saving the parent (Supplier) should also save the child (SupplierAddress)
Edit: I think you're actually using References incorrectly here.
In a mapping, when you say an entity References another, you're basically telling NHibernate that the other side of this relationship is a HasMany.
In your case, neither Supplier nor SupplierAddress actually has many SupplierAddresses or Suppliers, respectively.
With that in mind, you probably mean one of two things:
A SupplierAddress is shared by multiple Suppliers. This would mean that SupplierAddress actually has many Suppliers, but a Supplier only has one SupplierAddress.
In the C# class, this would mean that SupplierAddress has a collection of Suppliers (OR has no reference to Supplier at all).
In this case, your database tables would look like this:
create table [SupplierAddress]
(
[Id] int identity(1,1) primary key clustered,
[AddressLine1] nvarchar(255) not null
);
create table [Supplier]
(
[Id] int identity(1,1) primary key clustered,
[SupplierAddressId] int not null references [SupplierAddress]([Id])
)
Your C# classes would look like this:
public class Supplier
{
public virtual int Id { get; set; }
public virtual SupplierAddress SupplierAddress { get; set; }
public virtual string Name { get; set; }
}
public class SupplierAddress
{
public virtual int Id { get; set; }
public virtual string AddressLine1 { get; set; }
}
And your mappings would look like this:
public class SupplierMap : ClassMap<Supplier>
{
public SupplierMap()
{
Id(s => s.Id).GeneratedBy.Identity().Column("Id");
References(s => s.SupplierAddress)
.Column("SupplierAddressId")
.Cascade.All();
}
}
public class SupplierAddressMap : ClassMap<SupplierAddress>
{
public SupplierAddressMap()
{
Id(s => s.Id).GeneratedBy.Identity().Column("Id");
Map(s => s.AddressLine1)
.Column("AddressLine1")
.Not.Nullable()
.Length(255);
}
}
A Supplier has one SupplierAddress, and a SupplierAddress is only ever associated with one Supplier. Another way to think of this is that the entire SupplierAddress table could be logically merged into Supplier.
In this case, your database tables would look like this:
create table [SupplierAddress]
(
[Id] int identity(1,1) primary key clustered,
[AddressLine1] nvarchar(255) not null,
[SupplierId] int not null
);
create table [Supplier]
(
[Id] int identity(1,1) primary key clustered,
[SupplierAddressId] int references [SupplierAddress]([Id])
);
alter table [SupplierAddress]
add constraint [FK_SupplierAddress_Supplier]
foreign key ([SupplierId]) references [Supplier]([Id])
Your C# classes would look like this:
public class Supplier
{
private SupplierAddress supplierAddress;
public virtual int Id { get; set; }
public virtual SupplierAddress SupplierAddress
{
get { return this.supplierAddress; }
set
{
this.supplierAddress = value;
this.supplierAddress.Supplier = this;
}
}
public virtual string Name { get; set; }
}
public class SupplierAddress
{
public virtual int Id { get; set; }
public virtual string AddressLine1 { get; set; }
public virtual Supplier Supplier { get; set; }
}
And your mappings would look like this:
public class SupplierMap : ClassMap<Supplier>
{
public SupplierMap()
{
Id(s => s.Id).GeneratedBy.Identity().Column("Id");
HasOne(s => s.SupplierAddress).PropertyRef(s => s.Supplier)
.Access.CamelCaseField()
.Cascade.All();
}
}
public class SupplierAddressMap : ClassMap<SupplierAddress>
{
public SupplierAddressMap()
{
Id(s => s.Id).GeneratedBy.Identity().Column("Id");
Map(s => s.AddressLine1).Column("AddressLine1");
References(s => s.Supplier).Column("SupplierId").Unique();
}
}
Note that when Supplier.SupplierAddress is set, the address's Supplier property is set.

Related

Mapping a composite table to a table of default values in EF Core

I've got a simple structure in database:
CREATE TABLE user_categories (
user_id INT NOT NULL,
category_id INT NOT NULL,
PRIMARY KEY (user_id, category_id)
);
CREATE TABLE user_defaults (
user_id INT NOT NULL,
category_id INT NOT NULL,
PRIMARY KEY (user_id),
FOREIGN KEY (user_id, category_id) REFERENCES user_categories (user_id, category_id)
);
The intention here is that user has a set of categories assigned and there's a table with defaults table (which could potentially have other columns)
However I struggle to map this to EF Core, I always end up getting extra columns in user_categories table.
My UserCategory class:
using System.ComponentModel.DataAnnotations.Schema;
namespace EMMA.Authorization.Domain.Entities
{
public class UserCategory
{
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public int CategoryId { get; set; }
[ForeignKey(nameof(CategoryId))]
public Category Category { get; set; }
public UserDefaults UserDefaults { get; set; }
}
}
UserDefaults
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace EMMA.Authorization.Domain.Entities
{
public class UserDefaults
{
public int UserId { get; set; }
public int CategoryId { get; set; }
public virtual ICollection<UserCategory> UserCategories { get; set; }
}
}
That's OnModelCreating method that should configure relationships:
modelBuilder.Entity<UserCategory>().HasKey(s => new { s.UserId, s.CategoryId });
modelBuilder.Entity<UserDefaults>().HasKey(s => s.UserId);
modelBuilder.Entity<UserDefaults>()
.HasMany(s => s.UserCategories)
.WithOne(s => s.UserDefaults)
.HasForeignKey(s => new { s.UserId, s.CategoryId })
.IsRequired();
However that's the exception I'm getting when I try to create a migration:
The relationship from 'UserCategory.UserDefaults' to 'UserDefaults.UserCategories' with foreign key properties {'UserId' : int, 'CategoryId' : int} cannot target the primary key {'UserId' : int} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.
How could I configure such an relationship in EF Core as shown in SQL?
The user_catagories object should have a composite key. You are already impliying this by having the FK from the other table be a composite key on the first. EF complains because this is actually not true. You can fix it by annotating both members as primary like so:
public class UserCatagory
{
[Key, Column(Order = 0)]
public int UserId { get; set; }
[Key, Column(Order = 1)]
public int CategoryId { get; set; }
// Etc...
}
I actually realized that I did declare a wrong relationship, it's not one to many, but rather one to one. Declaring the following mapping works as expected:
modelBuilder.Entity<UserCategory>()
.HasOne(s => s.UserDefaults)
.WithOne(s => s.UserCategory)
.HasForeignKey<UserDefaults>(s => new { s.UserId, s.CategoryId })
.IsRequired();

Entity splitting with EF Code First issue

So I am trying to achieve entity splitting in EF 6.1 with Code First, and I am running into an error.
I have the following tables:
CREATE TABLE [dbo].[Organization]
(
[OrganizationId] INT NOT NULL PRIMARY KEY IDENTITY,
[TenantId] INT NOT NULL,
[Name] NVARCHAR(80) NOT NULL
)
CREATE TABLE [dbo].[OrganizationSettings]
(
[OrganizationSettingsId] INT NOT NULL PRIMARY KEY IDENTITY,
[OrganizationId] INT NOT NULL,
[AllowMultipleTimers] BIT NOT NULL,
CONSTRAINT [FK_OrganizationSettings_Organization] FOREIGN KEY (OrganizationId) REFERENCES Organization(OrganizationId)
)
With the following model objects:
public partial class Organization
{
public int OrganizationId { get; set; }
public int TenantId { get; set; }
public string Name { get; set; }
public OrganizationSettings Settings { get; set; }
}
public class OrganizationSettings
{
public int OrganizationSettingsId { get; set; }
public int OrganizationId { get; set; }
public bool AllowMultipleTimers { get; set; }
}
With the following config code:
var org = modelBuilder.Entity<Organization>();
org.Map(u =>
{
u.Properties(m => new { m.TenantId, m.Name });
})
.ToTable("Organization");
org.Map(u =>
{
u.Property(m => m.Settings.AllowMultipleTimers).HasColumnName("AllowMultipleTimers");
u.ToTable("OrganizationSettings");
});
Then just the following query:
context.Organizations.FirstOrDefault();
Which yields the following error:
The property 'Settings.AllowMultipleTimers' on type 'Organization'
cannot be mapped because it has been explicitly excluded from the
model or it is of a type not supported by the DbModelBuilderVersion
being used.
What am I doing wrong here?
Update: I forgot to mention that I created the database by hand, and am using the CF fluent API to map my models, rather than using "real" Code First.
While I was pretty sure I had this mapping working before, I went ahead and went a little different route.
First I got rid of the surrogate key on `OrganizationSettings (probably not strictly necessary), and then mapped it as an entity with a 1:1 relationship.
My OrganizationSettings is now:
public class OrganizationSettings
{
public int OrganizationId { get; set; }
public bool AllowMultipleTimers { get; set; }
}
OrganizationId is both a primary key and a foreign key.
And the config is:
var org = modelBuilder.Entity<Organization>()
.Map(u =>
{
u.Properties(m => new { m.TenantId, m.Name });
})
.HasRequired(m => m.Settings)
.WithRequiredPrincipal();
modelBuilder.Entity<OrganizationSettings>()
.HasKey(m => m.OrganizationId);
And this seems to work just fine. Since I'm not exposing a DbSet for OrganizationSettings it keeps the conceptual modeling of OrganizationSettings as a value object intact.
Were you trying to set up OrganizationSettings as a complex type while using entity splitting as well? Something like this, perhaps:
public partial class Organization
{
public int OrganizationId { get; set; }
public int TenantId { get; set; }
public string Name { get; set; }
public OrganizationSettings Settings { get; set; }
}
public class OrganizationSettings
{
public bool AllowMultipleTimers { get; set; }
}
// if you don't have a key defined on OrganizationSettings, this might not be needed
modelBuilder.ComplexType<OrganizationSettings>();
modelBuilder.Entity<Organization>()
.Map(u =>
{
u.Properties(m => new { m.OrganizationId, m.TenantId, m.Name });
u.ToTable("Organization");
})
.Map(u =>
{
u.Properties(m => new { m.OrganizationId, m.Settings.AllowMultipleTimers });
u.ToTable("OrganizationSettings");
// If you wanted to set the key column name
u.Property(m => m.OrganizationId).HasColumnName("OrganizationSettingsId");
});

mapping nhibernate parent/child relationship the 'other' way round

using FluentNhibernate;
I am trying to persist a seemingly simple object model:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Config Config { get; set; }
}
public class Config
{
public int ConfigId { get; set; }
public int ProductId { get; set; }
public string ConfigField1 { get; set; }
public string ConfigField2 { get; set; }
}
and the database looks like: (not syntactically correct)
CREATE TABLE [dbo].[Products](
[ProductId] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NULL
)
CREATE TABLE [dbo].[Config](
[ConfigId] [int] IDENTITY(1,1) NOT NULL,
[ProductId] [int] NOT NULL,
[ConfigField1] [varchar](50) NULL,
[ConfigField2] [varchar](50) NULL
) ON [PRIMARY]
Out of the box fluent-nhibernate will try and map this as a foreign key on the Products table, eg:
INSERT INTO Products (Name, Config_id) VALUES (?, ?);
I don't want it to do this, rather I was hoping the mapping would insert Products first then the config second with ProductId being inserted into the Config table.
I've pulled my hair out trying overrides and reading links such as this and this but still can't get it to do what I want. I am working with existing data and code so I would rather not change the database table definitions or the domain object. There is a bit more going on than what this example paints so if we could avoid discussions on domain model design that would be great. I have a link to a spike of this project here (assumes database exists)
my current fluent mappings are:
public class ProductOverrides : IAutoMappingOverride<Product>
{
public void Override(AutoMapping<Product> mapping)
{
mapping.Id(x => x.Id).Column("ProductId");
mapping.Table("Products");
}
}
public class ConfigOverrides : IAutoMappingOverride<Config>
{
public void Override(AutoMapping<Config> mapping)
{
mapping.Id(x => x.ConfigId);
}
}
You are trying to map a one-to-one relationship as a one-to-many by mapping what would be the many side twice. That won't work. NHibernate is strict in its definition of one-to-one and requires that both sides have the same primary key, so that won't work either.
I've butted heads with this before and the best workaround I found is to model it as a standard on-to-many but only allow one item in the collection by encapsulating access to the it. In your case I would guess that Product would be the one side and Config the many.
I'm not sure if Config is used elsewhere but you could ignore ConfigId as its identity
public class Config
{
public int Id { get; set; }
public Product Product { get; set; }
public string ConfigField1 { get; set; }
public string ConfigField2 { get; set; }
}
public class ProductMap : ClassMap<Product>
{
public class ProductMap()
{
HasOne(p => p.Config);
}
}
public class ConfigMap : ClassMap<Config>
{
public class ConfigMap()
{
Id(c => c.Id, "ProductId").GeneratedBy.Foreign("Product");
References(c => c.Product, "ProductId");
Map(c => ...);
}
}
Another idea is to join and map as Component
public class ProductMap : ClassMap<Product>
{
public class ProductMap()
{
Join("Config", join =>
{
join.KeyColumn("ProductId");
join.Component(p => p.Config, c =>
{
c.Map(...);
});
}
}
}
Disadvantage is that you can not query Config directly, you have to query through Product

NHibernate Mapping 1 Object To 2 Tables

We want to use NHibernate as our persistence layer in our application. We are also using Fluent NHibernate for the mappings.
We get Person data from a 3rd party and we need to save the data to our database. It works better in the code to have all properties on one object, but it makes more sense to save the data to 2 tables in our database.
Our object looks like this:
public class Person
{
public virtual long VersionNumber { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string IdentificationNumber { get; set; }
}
Our database tables look like this:
CREATE TABLE PersonVersion (
[PK] VersionNumber bigint NOT NULL,
[FK] PersonDemographicsId int NOT NULL
)
CREATE TABLE PersonDemographics (
[PK] PersonDemographicsId int NOT NULL,
IdentificationNumber nvarchar(9) NOT NULL,
FirstName nvarchar(100) NOT NULL,
LastName nvarchar(100) NOT NULL
)
When we receive new data, the version number can change, but the rest of the demographics could be the same. What we need NHibernate to do is save a new record to the PersonVersion table which links to the existing PersonDemographics record. If the demographics have changed, then we will create a new record in both tables. But most of the time, once we've downloaded the initial data, the demographics won't change as often as the version number. We need to keep track of all version numbers so that's why it's necessary to create a new PersonVersion record.
How would we accomplish this using NHibernate and mappings using Fluent NHibernate?
Also, as you can see, our Person object currently does not have a PersonDemographicsId because it is not needed by our application at all; it is just an ID for the table relationship which is needed in the database. In order to properly map this in NHibernate, do we have to add a PersonDemographicsId property on our Person object?
Thanks for the help!
This article http://ayende.com/blog/2327/multi-table-entities-in-nhibernate explains a way to map a single class to two tables in the database.
just an idea, maybe has to be tweaked
public class Person
{
public virtual int Id { get; set; }
internal protected virtual IList<long> VersionNumbers { get; set; }
public virtual long VersionNumber {
get { return VersionNumbers[VersionNumbers.Count - 1]; }
set { VersionNumbers.Add(value); }
}
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string IdentificationNumber { get; set; }
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("PersonDemographics");
Id(p => p.Id, "PersonDemographicsId").GeneratedBy.Assigned();
Map(p => p.FirstName);
Map(p => p.LastName);
Map(p => p.IdentificationNumber);
HasMany(p => p.VersionRecord)
.Table("PersonVersion")
.KeyColumn("PersonDemographicsId")
.Element("VersionNumber")
.OrderBy("VersionNumber")
.Cascade.All();
}
}

NHibernate + Fluent NHibernate exception

Problem:
There are searches that can be stored in the DB. Each search has a collection of filters. Also there are roles. Each role may have (nullable column) a default search assigned to it. Also, each search is visible to zero or many roles (many-to-many relationship).
When I try to access the search filters, NH tries to access filters.DefaultSearchId, which doesn't exist in filters table.
DB:
CREATE TABLE [dbo].[Searches]
(
Id int identity(1,1) primary key,
Description nvarchar(2000) not null
);
CREATE TABLE [dbo].[Filters]
(
Id int identity(1,1) primary key,
Description nvarchar(2000) not null,
SearchId int not null references Searches(Id)
);
CREATE TABLE [dbo].[Roles]
(
Id int identity(1,1) primary key,
Name nvarchar(255) not null,
DefaultSearchId int null references Searches(Id)
);
CREATE TABLE [dbo].[SearchesRoles]
(
SearchId int not null references Searches(Id),
RoleId int not null references Roles(Id)
);
Entities:
public class Search {
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual ICollection<Filter> Filters { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Filter {
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual Search Search { get; set; }
}
public class Role {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Search DefaultSearch { get; set; }
}
Mappings:
public class SearchMap : ClassMap<Search>{
public SearchMap() {
Table("Searches");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
HasMany(x => x.Filters).Inverse().Cascade.All().AsBag();
HasManyToMany(x => x.Roles).Table("SearchesRoles").ParentKeyColumn("SearchId").ChildKeyColumn("RoleId");
}
}
public class FilterMap : ClassMap<Filter> {
public FilterMap() {
Table("Filters");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
References(x => x.Search).Column("SearchId");
}
}
public class RoleMap : ClassMap<Role> {
public RoleMap() {
Table("Roles");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
References(x => x.DefaultSearch).Column("DefaultSearchId");
}
}
Code:
class Program {
static void Main() {
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession()) {
var search = session.Get<Search>(1);
foreach (var filter in search.Filters) {
Console.WriteLine(filter);
}
}
}
static ISessionFactory CreateSessionFactory(){
string connectionString = #"server=.\sql2008; user id = sa; pwd=1; database = nhbug;";
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
.Mappings(m=>m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())).BuildSessionFactory();
}
}
ERROR:
When accessing the search.Filters property, NHibernate tries to access Filters.DefaultSearchId db column which is not supposed to be there. This column exists in Roles table but not in filters.
QUESTION:
Is it invalid configuration, Fluent NHibernate or NHibernate bug?
I'm using SQL Server 2008 R2, NHibernate 2.1.2 and Fluent NHibernate 1.1.0.685, although this issue exists in NHibernate 3 beta 2 as well.
Thank you.
UPDATE:
Here is the actual SQL generated
UPDATE2: CDMDOTNET, same error, same sql, unfortunately.
UPDATE3: Actual exception
UPDATE4: This is a particular use case of a general bug: Entity references other entities as 'many-to-many' and on the other side of 'many-to-many' assoc. the other entity references the source entity (DefaultQuery in my case). NH goes nuts when accessing any child collection (one-to-many) of a source entity (Filters in my case).
UPDATE5: Sample data
UPDATE6: XML issued by Fluent NHibernate
Update the HasMany mapping on the SearchMap to include the KeyColumn():
HasMany(x => x.Filters).KeyColumn("SearchId").Inverse().Cascade.All().AsBag();
You didn't specify the column name for the Filters bag. It should be set to SearchId.

Categories

Resources