Inserting records in postgres using NHibernate 3.2 (loquacious) and bycode mappings - nhibernate

I am working on a very basic NHibernate 3.2 task, inserting records into existing Postgres tables. I am using very simple object so that they make sense for this question.
The postgres tables are as follows:
CREATE TABLE cat
(
id serial NOT NULL,
"name" character varying(50) NOT NULL,
sex_id integer NOT NULL,
CONSTRAINT cat_pkey PRIMARY KEY (id),
CONSTRAINT cat_sex_id_fkey FOREIGN KEY (sex_id)
REFERENCES sex (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
CREATE TABLE sex
(
id serial NOT NULL,
"name" character varying(10) NOT NULL,
CONSTRAINT sex_pkey PRIMARY KEY (id),
CONSTRAINT sex_name_key UNIQUE (name)
)
WITH (
OIDS=FALSE
);
My mapping classes are as follows:
public class CatMap : ClassMapping<Cat>
{
public CatMap()
{
Table("cat");
Id(x => x.Id, map =>
{
map.Column("id");
map.Generator(NHibernate.Mapping.ByCode.Generators.Native);
});
Property(x => x.Name, map =>
{
map.Column("name");
map.Length(50);
map.NotNullable(true);
});
ManyToOne(x => x.Sex, map =>
{
map.Column("Sex");
map.Unique(true);
map.ForeignKey("cat_sex_id_fkey");
});
}
}
public class SexMap : ClassMapping<Sex>
{
public SexMap()
{
Table("sex");
Id(x => x.Id, map =>
{
map.Column("id");
map.Generator(Generators.Native);
});
Property(x => x.Name, map =>
{
map.Column("name");
map.Unique(true);
map.Length(10);
map.NotNullable(true);
});
}
}
My data classes are as follows:
public class Sex
{
public Sex()
{
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Cat
{
public Cat()
{
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Sex Sex { get; set; }
}
Finally, the class containing the code where I am actually attempting to use all of the above.
public class Class1
{
public string DoSomething()
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
Postgres.Tables.Sex sex1 = new Postgres.Tables.Sex() { Name = "Male" };
Postgres.Tables.Sex sex2 = new Postgres.Tables.Sex() { Name = "Female" };
Postgres.Tables.Cat cat1 = new Postgres.Tables.Cat() { Name = "Cat1" };
cat1.Sex = sex1;
Postgres.Tables.Cat cat2 = new Postgres.Tables.Cat() { Name = "Cat2" };
cat2.Sex = sex2;
session.SaveOrUpdate(sex1);
session.SaveOrUpdate(sex2);
session.SaveOrUpdate(cat1);
session.SaveOrUpdate(cat2);
transaction.Commit();
}
}
return "I created the cats.";
}
private static ISessionFactory CreateSessionFactory()
{
NHibernate.Mapping.ByCode.ModelMapper modelMapper = new NHibernate.Mapping.ByCode.ModelMapper();
System.Type[] mappingTypes = typeof(Postgres.Tables.Mappings.CatMap).Assembly.GetExportedTypes().Where(t => t.Name.EndsWith("Map")).ToArray();
modelMapper.AddMappings(mappingTypes);
Configuration cfg = new Configuration();
cfg.Proxy(p => p.ProxyFactoryFactory<NHibernate.Bytecode.DefaultProxyFactoryFactory>());
cfg.DataBaseIntegration(d =>
{
d.ConnectionString = "server=192.168.1.126;Port=5432;Database=simple;User Id=postgres;Password=postgres;";
d.Dialect<NHibernate.Dialect.PostgreSQL82Dialect>();
});
cfg.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities());
return cfg.BuildSessionFactory();
}
}
I receive a GenericADOException at "session.SaveOrUpdate(cat1)" with the message "could not insert: [DAL.Postgres.Tables.Cat][SQL: INSERT INTO cat (name, Sex) VALUES (?, ?); select lastval()]". The InnerException is "{"ERROR: 42703: column \"sex\" of relation \"cat\" does not exist"}".
I am a bit stumped at how to properly assign "sex1" to "cat1" and "sex2" to "cat2" so that the first is Male and the second is Female.
Thank you for any input.

m.Column("Sex") as the name suggests denote column name and not the property name as you specified. So you should write m.Column("sex_id"). map.ForeignKey("cat_sex_id_fkey") is used by NH as a FK name when NH create db schema from your mapping.

Related

One-To-one Database First EF

Dear fellow programmers,
I'm stuck on this basic concept within EF and can't find any solution on stackoverflow.
I want to have One-to-One optional relation between: FluxLocation and Address.
(Normal words: a flux location could be provided with a physical address)
Note the database is already present and final.
SQL TABLES:
CREATE TABLE sales.sales_flux_location(
id serial PRIMARY KEY,
-- Many unusefull properties
sales_address_id integer REFERENCES sales_address
);
CREATE TABLE sales.sales_address(
id serial PRIMARY KEY,
-- Many unusefull properties
);
EF Mapping:
public partial class FluxLocation
{
public int Id { get; set; }
//Many unusefull properties.
[ForeignKey("Address")]
public int? AddressId { get; set; }
public Address Address { get; set; }
}
internal partial class FluxLocationConfiguration : EntityTypeConfiguration<FluxLocation>
{
public FluxLocationConfiguration()
{
//PK
HasKey(x => x.Id);
ToTable("sales_flux_location", "sales");
Property(a => a.Id)
.HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
//FK
HasOptional(l => l.Address)
.WithOptionalDependent(a => a.FluxLocation);
Property(l => l.AddressId)
.HasColumnName("sales_address_id")
.IsOptional();
// + mapping other properties.
}
public partial class Address
{
public int Id { get; set; }
// other properties
public FluxLocation FluxLocation { get; set; }
}
internal partial class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
//PK
HasKey(a => a.Id);
ToTable("sales_address", "sales");
Property(a => a.Id)
.HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
//FK
HasOptional(a => a.FluxLocation).WithOptionalPrincipal(l=>l.Address);
// mapping many unusefull properties
}
TEST CASE:
var dbAddress = Context.AddressSet.Add(new Address {Country = "BEL", CityName="Brussel", Street = Guid.NewGuid().ToString() });
var dbLocation = Context.FluxLocationSet.Add(new FluxLocation { AddressId = dbAddress.Id, Country = "BEL", Type = "MARKET", ExtId = Guid.NewGuid().ToString() });
Context.SaveChanges();
Error on Context.SaveChanges():
"42703: column \"Address_Id\" of relation \"sales_flux_location\" does not exist"}
Which is correct because the column name is "sales_address_id".
If any one could help why he is ignoring the propery columnname mapping?
I'm happy to provide more code if needed.
EF is not picking up that you want sales_address_id as the FK so it tried to create Address_Id. Also, there is some weirdness in how EF does 0:1 - essentially you need to fool it with a 1:M
So try this:
//FK
HasOptional(l => l.Address)
.WithMany()
.HasForeignKey(d => d.AddressId);
Link

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;

Mapping by code on Class that has a property of type ICollection<>

I want to map a class that has a property of type ICollection<> using NHibernate mapping by code. The code below works. But I don't like the extra Person property within CarSet to make the mapping work.
public class PersonSet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<CarSet> Cars { get; set; }
}
public class CarSet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual PersonSet Person { get; set; }
}
public class PersonSetMap : ClassMapping<PersonSet>
{
public PersonSetMap()
{
Id(x => x.Id, m=>m.Generator(Generators.Identity));
Property(x=>x.Name);
Set(x => x.Cars, c =>
{
c.Key(k =>
{
k.Column("PersonId");
});
c.Cascade(Cascade.Persist);
c.Lazy(CollectionLazy.NoLazy);
}, r =>
{
r.OneToMany();
}
);
}
}
public class CarSetMap : ClassMapping<CarSet>
{
public CarSetMap()
{
Id(x => x.Id, m => m.Generator(Generators.Identity));
Property(x => x.Name);
ManyToOne(x => x.Person, m =>
{
m.Column("PersonId");
m.Cascade(Cascade.None);
m.NotNullable(true);
});
}
}
public void Save(){
using (var session = Cfg.Session)
using (var tx = session.BeginTransaction())
{
PersonSet John = new PersonSet { Name = PersonName.John };
John.Cars = new List<CarSet> {
new CarSet { Name = CarnName.BMW,Person = John},
new CarSet { Name = CarnName.BM,Person = John }};
session.Save(entity);
tx.Commit();
}
}
The code above generates SQL script below:
create table PersonSet (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
primary key (Id)
)
create table CarSet (
Id INT IDENTITY NOT NULL,
Name NVARCHAR(255) null,
PersonId INT not null,
primary key (id)
)
alter table CarSet
add constraint FKF967D6489A220265
foreign key (PersonId)
references PersonSet
What I want is to generate SQL script with difference shown below, and keep the rest the same:
create table CarSet (
Name NVARCHAR(255) null,
PersonId INT not null,
)
Ideally I want the CarSet like this instead:
public class CarSet
{
public virtual int PersonId { get; set; }
public virtual string Name { get; set; }
}
Any idea?
map Cars as ComponentCollection
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Car> Cars { get; set; }
}
class Car
{
public virtual Person Owner { get; set; }
public virtual string Name { get; set; }
}
public class PersonMap : ClassMapping<Person>
{
public PersonMap()
{
Id(x => x.Id, m => m.Generator(Generators.Identity));
Property(x => x.Name);
Set(x => x.Cars, c =>
{
c.Key(k => k.Column("PersonId"));
c.Cascade(NHibernate.Mapping.ByCode.Cascade.Persist);
c.Lazy(CollectionLazy.NoLazy);
}, r =>
{
r.Component(c =>
{
c.Parent(x => x.Owner);
c.Property(x => x.Name);
});
});
}
}
Your ideal solution isn't possible. To use a CarSet table without it's own ID column it has to be a component, but component sets can't have nullable columns. If it's ok for you to mark Name as not-null you can adapt the solution Firo posted.
If that's not ok you can at least solve your first request to remove the Person property. Just delete the property and mark the key column in your set mapping as not-nullable. CarSet will still be an entity (and therefore have it's own ID) but you don't need the reference to PersonSet in code.
Btw, why are your classes postfixed by Set? Just naming them Person and Car would be much better since they only represent one person or car, not a collection of them.

NHibernate one-to-one: null id generated for AccountDetail

I got an exception "null id generated for AccountDetail" when mapping one-to-one relationship by using many-to-one with unique constraint.
Here's my SQL tables:
Account(Id, Name)
AccountDetail(AccountId, Remark)
AccountId is both primary and foreign key.
Here's my Domain Model (Account and AccountDetail):
public class Account
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual AccountDetail Detail { get; set; }
public Account()
{
Detail = new AccountDetail
{
Account = this
};
}
}
public class AccountDetail
{
public virtual int AccountId { get; set; }
public virtual Account Account { get; set; }
public virtual string Remark { get; set; }
}
Mapping (NHibenrate 3.3 mapping by code):
class AccountMap : ClassMapping<Account>
{
public AccountMap()
{
Table(typeof(Account).Name);
Id(c => c.Id, m => m.Generator(Generators.Native));
Property(c => c.Name);
OneToOne(c => c.Detail, m =>
{
m.Constrained(true);
m.Cascade(Cascade.All);
m.PropertyReference(typeof(AccountDetail).GetPropertyOrFieldMatchingName("Account"));
});
}
}
class AccountDetailMap : ClassMapping<AccountDetail>
{
public AccountDetailMap()
{
Table(typeof(AccountDetail).Name);
Id(c => c.AccountId, m =>
{
m.Column("AccountId");
m.Generator(Generators.Foreign<AccountDetail>(x => x.Account));
});
Property(c => c.Remark);
ManyToOne(c => c.Account, m =>
{
m.Column("AccountId");
m.Unique(true);
});
}
}
BTW: Can I remove the AccountId property in AccountDetail? That is, only use the Account property. Using both AccountId and Account properties in AccountDetail class looks not so object-oriented.
Thanks!
I can't say what's actually wrong, but comparing with my working one-to-one relation, I would map it like this:
class AccountMap : ClassMapping<Account>
{
public AccountMap()
{
Table(typeof(Account).Name);
// creates a auto-counter column "id"
Id(c => c.Id, m => m.Generator(Generators.Native));
// doesn't require a column, one-to-one always means to couple primary keys.
OneToOne(c => c.Detail, m =>
{
// don't know if this has any effect
m.Constrained(true);
// cascade should be fine
m.Cascade(Cascade.All);
});
}
}
class AccountDetailMap : ClassMapping<AccountDetail>
{
public AccountDetailMap()
{
Id(c => c.AccountId, m =>
{
// creates an id column called "AccountId" with the value from
// the Account property.
m.Column("AccountId");
m.Generator(Generators.Foreign(x => x.Account));
});
// should be one-to-one because you don't use another foreign-key.
OneToOne(c => c.Account);
}
}

Can I make a Fluent NHibernate foreign key convention which includes parent key name?

I have a database schema where the convention for a foreign key's name is:
ForeignTable.Name + ForeignTable.PrimaryKeyName
So, for a Child table referencing a Parent table with a primary key column named Key, the foreign key will look like ParentKey.
Is there a way to create this convention in my Fluent NHibernate mapping?
Currently I'm using a ForeignKeyConvention implementation like this:
public class ForeignKeyNamingConvention : ForeignKeyConvention
{
protected override string GetKeyName(PropertyInfo property, Type type)
{
if (property == null)
{
// Relationship is many-to-many, one-to-many or join.
if (type == null)
throw new ArgumentNullException("type");
return type.Name + "ID";
}
// Relationship is many-to-one.
return property.Name + "ID";
}
}
This works exactly as I want for all types which have "ID" as a primary key. What I would like to do is replace the constant "ID" with the name of the primary key of the type being referenced.
If this isn't currently possible with Fluent NHibernate, I'm happy to accept that answer.
Take a look at conventions and especially at implementing a custom foreign key convention.
UPDATE:
Here's an example. Assuming the following domain:
public class Parent
{
public virtual int Id { get; set; }
}
public class Child
{
public virtual string Id { get; set; }
public virtual Parent Parent { get; set; }
}
which needs to be mapped to this schema:
create table Child(
Id integer primary key,
ParentId integer
)
create table Parent(
Id integer primary key
)
you could use this convention:
public class CustomForeignKeyConvention : IReferenceConvention
{
public void Apply(IManyToOneInstance instance)
{
instance.Column(instance.Class.Name + "Id");
}
}
and to create the session factory:
var sf = Fluently
.Configure()
.Database(
SQLiteConfiguration.Standard.UsingFile("data.db3").ShowSql()
)
.Mappings(
m => m.AutoMappings.Add(AutoMap
.AssemblyOf<Parent>()
.Where(t => t.Namespace == "Entities")
.Conventions.Add<CustomForeignKeyConvention>()
)
)
.BuildSessionFactory();
If you can get the Mapping<T> for a class, you can get the name of its Id column.
public class MyForeignKeyConvention: ForeignKeyConvention
{
public static IList<IMappingProvider> Mappings = new List<IMappingProvider>();
protected override string GetKeyName( System.Reflection.PropertyInfo property, Type type )
{
var pk = "Id";
var model = new PersistenceModel();
foreach( var map in Mappings ) {
model.Add( map );
}
try {
var mymodel = (IdMapping) model.BuildMappings()
.First( x => x.Classes.FirstOrDefault( c => c.Type == type ) != null )
.Classes.First().Id;
Func<IdMapping, string> getname = x => x.Columns.First().Name;
pk = getname( mymodel );
} catch {
}
if (property == null) {
return type.Name + pk;
}
return type.Name + property.Name;
}
}
We can get the Mapping object with a little bit of plumbing.
The constructors of ClassMap<T> can pass this into our collection of Mappers.
For AutoMapping<T>, we can use Override as follows.
.Mappings( m => m.AutoMappings.Add( AutoMap.AssemblyOf<FOO>()
.Override<User>( u => {
u.Id( x => x.Id ).Column( "UID" );
MyForeignKeyConvention.Mappings.Add( u );
}
)
For a system wide convention I believe this would serve the purpose best.
( I wasn't sure whether to include the whole text or just a portion here, since I answered it here already)
Here's the solution with links to current Fluent NHibernate & automapping documentation.
The issue (a simple example):
Say you have the simple example (from fluent's wiki) with an Entity and it's Value Objects in a List:
public class Product
{
public virtual int Id { get; set; }
//..
public virtual Shelf { get; set; }
}
public class Shelf
{
public virtual int Id { get; set; }
public virtual IList<Product> Products { get; set; }
public Shelf()
{
Products = new List<Product>();
}
}
With tables which have e.g.
Shelf
id int identity
Product
id int identity
shelfid int
And a foreign key for shelfid -> Shelf.Id
You would get the error:
invalid column name ... shelf_id
Solution:
Add a convention, it can be system wide, or more restricted.
ForeignKey.EndsWith("Id")
Code example:
var cfg = new StoreConfiguration();
var sessionFactory = Fluently.Configure()
.Database(/* database config */)
.Mappings(m =>
m.AutoMappings.Add(
AutoMap.AssemblyOf<Product>(cfg)
.Conventions.Setup(c =>
{
c.Add(ForeignKey.EndsWith("Id"));
}
)
.BuildSessionFactory();
Now it will automap the ShelfId column to the Shelf property in Product.
More info
Wiki for Automapping
Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")
See more about Fluent NHibernate automapping conventions