I want to mapping following simple entities:
public class AnimalsOwner
{
public AnimalsOwner()
{
Cats = new List<Cat>();
Dogs = new List<Dog>();
}
public virtual int Id { get; set; }
public virtual IList<Cat> Cats { get; set; }
public virtual IList<Dog> Dogs { get; set; }
}
public abstract class Animal
{
public virtual int Id { get; set; }
public virtual AnimalsOwner AnimalsOwner { get; set; }
}
public class Dog : Animal
{
public virtual string DogsProperty { get; set; }
}
public class Cat : Animal
{
public virtual string CatsProperty { get; set; }
}
With the following mappings:
public OwnerMapping()
{
this.Table("Owners");
Id(x => x.Id)
.GeneratedBy.Native();
HasMany(x => x.Cats).AsBag().Cascade.SaveUpdate();
HasMany(x => x.Dogs).AsBag().Cascade.SaveUpdate();
}
}
public class AnimalMapping : ClassMap<Animal>
{
public AnimalMapping()
{
this.Table("Animals");
Id(x => x.Id).GeneratedBy.Native();
References(x => x.AnimalsOwner).Not.Nullable();
}
}
public class DogMap : SubclassMap<Dog>
{
public DogMap()
{
this.Table("Dogs");
this.KeyColumn("AnimalId");
Map(x => x.DogsProperty);
}
}
public class CatMap : SubclassMap<Cat>
{
public CatMap()
{
this.Table("Cats");
this.KeyColumn("AnimalId");
Map(x => x.CatsProperty);
}
}
With such mappings everything works fine, but generated schema looks like following:
And such code:
var owner = new AnimalsOwner();
owner.Cats.Add(new Cat()
{
AnimalsOwner = owner,
CatsProperty = "cat"
});
Makes inserts in all three tables (Animals, Cats, Dogs) of column OwnderId. But isn't it enough to have it only in Animals table? If yes then how to achieve it? If NHibernate acts as expected then why is he doing such a duplication?
I would map this using table-per-class strategy and filter the public collections by type. This database structure is much easier to work with and more efficient to query.
public class AnimalMapping : ClassMap<Animal>
{
public AnimalMapping()
{
Table("Animals");
Id(x => x.Id).GeneratedBy.Native();
References(x => x.AnimalsOwner);
DiscriminateSubClassesOnColumn("AnimalType");
}
}
public class DogMap : SubclassMap<Dog>
{
public DogMap()
{
DiscriminatorValue("DOG");
Map(x => x.DogsProperty);
}
}
public class CatMap : SubclassMap<Cat>
{
public CatMap()
{
DiscriminatorValue("CAT");
Map(x => x.CatsProperty);
}
}
The Dogs and Cats collection can be exposed by mapping the Animals collection as a private member and filtering by type:
public class AnimalsOwner
{
private IList<Animal> _animals;
public AnimalsOwner()
{
_animals = new List<Animal>();
}
public virtual int Id { get; set; }
public virtual IEnumerable<Animal> Animals { get { return _animals; } }
public virtual IEnumerable<Cat> Cats { get { return _animals.OfType<Cat>(); } }
public virtual IEnumerable<Dog> Dogs { get { return _animals.OfType<Dog>(); } }
public virtual void AddAnimal(Animal animal)
{
if (!_animals.Contains(animal))
{
animal.AnimalsOwner = this;
_animals.Add(animal);
}
}
public virtual void RemoveAnimal(Animal animal)
{
if (_animals.Contains(animal))
{
animal.AnimalsOwner = null;
_animals.Remove(animal);
|
}
}
The mapping for this class would be:
public OwnerMapping()
{
this.Table("Owners");
Id(x => x.Id).GeneratedBy.Native();
HasMany(x => x.Animals.AsBag.Cascade.AllDeleteOrphan()
.Inverse().LazyLoad()
.Access.CamelCaseField(Prefix.Underscore);
}
}
And the code for adding a cat would be:
var owner = new AnimalsOwner();
owner.AddAnimal(new Cat()
{
CatsProperty = "cat"
});
You're getting the duplicates becuse you have HasMany Cats and Dogs defined in the owner mapping (and Cat & Dog collections in the Owner class). Perhaps what you really want is a collection of Animals in the owners class (not individually dogs and cats) and just one HasMany Animals in the Owner mapping?
Related
I have a three generation class structure that I am attempting to ORM-ify using Fluent Nhibernate. To summarize the classes they look as such...
public class Highest{
public virtual Guid Id{
get;
set;
}
public virtual IList<Medium> Children{
get;
set;
}
}
public class Medium{
public virtual int Index{
get;
set;
}
public virtual Highest Parent{
get;
set;
}
public virtual IList<Lowest> Children{
get;
set;
}
}
public class Lowest{
public virtual MyEnum LowType{
get;
set;
}
public virtual Medium Parent{
get;
set;
}
}
I have attempted a number of tries to create the appropriate Maps using a simple Id for the highest level and composite Id for both the Medium and lowest levels. But all my strategies have failed for creating a compositeid for the lowest to the Medium since (I presume) it in turn uses a CompositeId from itself to Highest.
The Question!
How should the maps be designed such that I am certain that when I load a Highest class all of Medium and Lower relationships will also be created, including the Parent/Children relationships.
Thanks!
the mapping you requested
public class HighestMap : ClassMap<Highest>
{
public HighestMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
HasMany(x => x.Children)
.KeyColumn("parent_id");
}
}
public class MediumMap : ClassMap<Medium>
{
public MediumMap()
{
CompositeId()
.KeyReference(x => x.Parent, "parent_id")
.KeyProperty(x => x.Index, "indexColumn");
HasMany(x => x.Children)
.KeyColumns.Add("medium_id", "indexColumn")
.Component(c =>
{
c.ParentReference(x => x.Parent);
c.Map(x => x.LowType);
});
}
}
However you said you want to load all in one go then it might be worth sacrificing queryability for performance. The following code will save the Medium.Children collection as a string in the database and is only queryable with equal and unequal session.Query<Medium>().Where(x => x.Children == myCollection);
public class Medium
{
public virtual Highest Parent { get; set; }
public virtual ICollection<MyEnum> Children { get; set; }
}
public class HighestMap : ClassMap<Highest>
{
public HighestMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
HasMany(x => x.Children)
.KeyColumn("parent_id")
.AsList(i => i.Column("indexColumn"))
.Component(c =>
{
c.ParentReference(x => x.Parent);
// remove Index property and use Highest.Children.IndexOf(medium)
c.Map(x => x.Children).CustomType<EnumCollectionAsStringUserType<MyEnum>>();
});
}
}
[Serializable]
public class EnumCollectionAsStringUserType<TEnum> : IUserType
{
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var value = (string)NHibernateUtil.String.Get(rs, names[0]);
if (string.IsNullOrEmpty(value))
return new List<TEnum>();
return value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(Parse).ToList();
}
private static TEnum Parse(string arg)
{
return (TEnum)Enum.Parse(typeof(TEnum), arg);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var collection = (IEnumerable<TEnum>)value;
NHibernateUtil.String.Set(cmd, string.Join(",", collection), index);
}
public Type ReturnedType { get { return typeof(ICollection<TEnum>); } }
public SqlType[] SqlTypes { get { return new[] { SqlTypeFactory.GetString(255) }; } }
public object DeepCopy(object value)
{
return new List<TEnum>((IEnumerable<TEnum>)value);
}
bool IUserType.Equals(object x, object y)
{
return ((IEnumerable<TEnum>)x).SequenceEqual((IEnumerable<TEnum>)y);
}
int IUserType.GetHashCode(object x)
{
return ((IEnumerable<TEnum>)x).Aggregate(0, (a, v) => a << 8 + v.GetHashCode());
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public bool IsMutable { get { return true; } }
public object Replace(object original, object target, object owner)
{
return original;
}
}
I have the following:
public abstract class FooBase
{
public virtual int Id { get; set; }
}
public class Foo1 : FooBase { /* could be stuff here */ }
public class Foo2 : FooBase { /* could be stuff here */ }
public class Bar
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual SOMETHING WhichFoo { get; set }
}
...where WhichFoo deals with which FooBase to use (potentially string of "Foo1" but this seems quite messy).
With mappings:
public class FooBaseMap : ClassMap<FooBase>
{
public FooBaseMap()
{
this.Id(x => x.Id);
this.DiscriminateSubClassesOnColumn("Type");
}
}
public class Foo1Map : SubclassMap<Foo1>
{
public Foo1Map() { this.DiscriminatorValue("Foo1"); }
}
public class Foo2Map : SubclassMap<Foo2>
{
public Foo2Map() { this.DiscriminatorValue("Foo2"); }
}
What I want to be able to do is to create a new Foo, either 1 or 2, based on a value stored in Bar. So:
Bar bar = this.Session.Get<Bar>(1);
FooBase foo1 = bar.GetANewFoo(); // returns a new Foo1
Bar anotherBar = this.Session.Get<Bar>(123);
FooBase foo2 = bar.GetANewFoo(); // returns a new Foo2
Where GetANewFoo() could be a method, a property which returns an empty instance of Foo1 or Foo2.
Effectively what I want to do is to store the type of FooBase to be created by GetANewFoo in Bar.
What's the best way of going about this without explicitly having to manually write "Foo1" or "Foo2" to a Bar when I create one?
You can create a column in the Bar table which is going to save what kind of Foo it is. So, if this SOMETHING type of property is an enum, or a string, or an int (whatever), you can use switch-case statement to identify the kind of Foo, create what you need and return.
Just like this:
public FooBase GetANewFoo()
{
switch (WhichFoo)
{
case "Foo1": return new Foo1();
case "Foo2": return new Foo2();
default: return null;
}
}
Okay I think I got it - actually rehashing a method I used for the Strategy Pattern.
public abstract class FooFactoryBase
{
protected FooFactoryBase() { } // for NHibernate
protected FooFactoryBase(Guid id)
{
this.Id = id;
}
public virtual Guid Id { get; set; }
public virtual IList<Bar> Bars { get; set; }
public abstract FooBase CreateFoo();
}
public class Foo1Factory : FooFactoryBase
{
public readonly static Guid Guid = new Guid("abc123...");
public Foo1Factory() : base(Guid) { }
public override FooBase CreateFoo()
{
return new Foo1();
}
}
Then Bar becomes:
public class Bar
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual FooFactoryBase FooFactory { get; set; }
}
And mappings:
public class BarMap : ClassMap<Bar>
{
this.Id(x => x.Id);
this.Map(x => x.Name);
this.References(x => x.FooFactory);
}
public class FooFactoryBaseMap : ClassMap<FooFactoryBase>
{
this.Id(x => x.Id);
this.HasMany(x => x.Bars).Inverse();
this.DiscriminateSubClassesOnColumn("Id");
}
public class Foo1FactoryMap : SubClassMap<Foo1Factory>
{
this.DiscriminatorValue(Foo1Factory.Guid);
}
Now when I create my database I can populate it with all my FooFactorys, when adding Bars I can just load the appropriate Factory from the database and then call my code like so:
Bar bar = this.Session.Get<Bar>(10);
FooBase foo = bar.FooFactory.CreateFoo();
and the appropriate FooBase will be created.
Sometimes you just need to ask the question to figure it out :)
Problem seems pretty generic, but I am new to fluentnhibernate and all my searches did not turn up anything useful.
I have a class hierarchy similar to this one:
public class Person
{
public virtual int Id { get; protected set; }
public virtual string GivenName { get; set; }
public virtual string FamilyName { get; set; }
}
public class Employee : Person
{
public virtual string WhoAmI { get { return "I am just an Employee"; } protected set { } }
}
public class Boss : Employee
{
public override string WhoAmI { get { return "I am the boss."; } protected set { } }
}
I.e. one base class and two subclasses (one derived from the other). The subclasses have the same signature, but the base class has not.
The mapping below will put all information about the instances of the three classes into the same table:
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.Id);
Map(x => x.GivenName);
Map(x => x.FamilyName);
DiscriminateSubClassesOnColumn("type");
}
}
public class EmployeeMap : SubclassMap<Employee>
{
public EmployeeMap()
{
Map(x => x.WhoAmI);
}
}
public class BossMap : SubclassMap<Boss>
{
public BossMap()
{
}
}
Leaving out the DiscriminateSubClassesOnColumn will generate three tables:
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.Id);
Map(x => x.GivenName);
Map(x => x.FamilyName);
}
}
public class EmployeeMap : SubclassMap<Employee>
{
public EmployeeMap()
{
Map(x => x.WhoAmI);
}
}
public class BossMap : SubclassMap<Boss>
{
public BossMap()
{
}
}
My question: Is it possible (in fluentnhibernate) to map these classes into exactly two tables, i.e. to have
one Person table that holds all the properties derived from the Person class, and
an Employee table that holds all the properties derived from the Employee class, plus a discriminator that discriminates between normal Employees and Bosses?
If you simply alias BossMap to EmployeeMap:
public class BossMap : EmployeeMap {}
you should get the mapping that you need.
I'm using nHibernate and trying to add some master-detail data. The database diagram is attached here.
The following is the code for adding a category along with two products:
//Category
namespace Sample.CustomerService.Domain
{
public class Category {
public Category() {
Products = new List<Product>();
}
public virtual int CategoryID { get; set; }
public virtual IList<Product> Products { get; set; }
public virtual string Name { get; set; }
public virtual string Unit { get; set; }
public virtual void AddProduct(Product product)
{
Products.Add(product);
}
}
}
//CategoryMap
namespace Sample.CustomerService.Domain
{
public class CategoryMap : ClassMap<Category> {
public CategoryMap() {
Table("Category");
LazyLoad();
Id(x => x.CategoryID).GeneratedBy.Identity().Column("CategoryID");
Map(x => x.Name).Column("Name").Not.Nullable().Length(50);
Map(x => x.Unit).Column("Unit").Not.Nullable().Length(3);
HasMany(x => x.Products).KeyColumn("CategoryID");
}
}
}
//--------------------------------------------------------------------------------------------
//Product
namespace Sample.CustomerService.Domain
{
public class Product {
public Product() {
}
public virtual int ProductID { get; set; }
public virtual Category Category { get; set; }
public virtual string Name { get; set; }
public virtual decimal UnitPrice { get; set; }
}
}
//ProductMap
namespace Sample.CustomerService.Domain {
public class ProductMap : ClassMap<Product> {
public ProductMap() {
Table("Product");
LazyLoad();
Id(x => x.ProductID).GeneratedBy.Identity().Column("ProductID");
References(x => x.Category).Column("CategoryID");
Map(x => x.Name).Column("Name").Not.Nullable().Length(50);
Map(x => x.UnitPrice).Column("UnitPrice").Not.Nullable();
}
}
}
//----------------------------------------------------------------------------------------------
//Program
namespace WindowsHibernateTest
{
public partial class TestClass : Form
{
public TestClass()
{
InitializeComponent();
CreateNewProduct();
}
public void CreateNewProduct()
{
try
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var sqlTrans = session.BeginTransaction())
{
Category newGold = new Category() { Name = "Gold", Unit = "GRM" };
Product ngOrn = new Product() { Name = "Bangles", UnitPrice = 1000.10M };
Product ogOrn = new Product() { Name = "Rings", UnitPrice = 2000.10M };
AddProductsToCategory(newGold, ngOrn, ogOrn);
session.SaveOrUpdate(newGold);
sqlTrans.Commit();
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure().Database(MsSqlConfiguration.MsSql2005.
ConnectionString("Data Source=MyDB; Initial Catalog=NHibernateTest; Trusted_Connection=true;")).Mappings(m => m.FluentMappings.AddFromAssemblyOf<Program>()).BuildSessionFactory();
}
public static void AddProductsToCategory(Category category, params Product[] products)
{
foreach (var product in products)
{
category.AddProduct(product);
}
}
}
}
In the line sqlTrans.Commit(), I'm getting the following error:
object references an unsaved transient instance - save the transient
instance before flushing. Type: Sample.CustomerService.Domain.Product,
Entity: Sample.CustomerService.Domain.Product
I've Googled for quite sometime and didn't get convincing solutions for this. I've tried this, this and this, but could not able to resolve this error. Please help me to resolve this error. Thanks in advance.
I think you need to save the products before you save the category because you don't have a Cascade on the HasMany.
And you need to change the add product methods to keep the relation
foreach (var product in products)
{
category.AddProduct(product);
product.Category = category;
}
I'm using fluent Nhibernate to map a simple class
And using Schema Generation to create this class on MySQL DB.
I can't use IList<> for my properties (I'm mapping cross-language domain classes)
So I have to use simple arrays..
I Want NHibernate to create a connection table between the two classes,
These are the domain classes:
public class ClassOne
{
public virtual Guid Guid { get; set; }
public virtual String Title { get; set; }
public virtual ClassTwo[] Tags { get; set; }
}
public class ClassTwo
{
public virtual Guid Guid { get; set; }
public virtual string Title { get; set; }
}
And this is the map:
public class ClassOneMap : ClassMap<ClassOneMap>
{
public ClassOneMap ()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
HasManyToMany(x => x.Tags)
.Cascade.SaveUpdate());
}
}
public class ClassTwoMap : ClassMap<ClassTwo>
{
public ClassTwoMap()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
}
}
The schema generates great! It has a ClassOne, ClassTwo and ClassTwoToClassOne Tables
But when I'm trying to persist an instance of ClassOne I have an Invalid Cast exception..
This is solved by changing the arrays to IList's but I can't really do that..
Can anyone tell me how to configure the Fluent mapping to use Arrays without changing the schema architecture?
Thanks A'lot!
Ok, played around this and hope that solve the question.
So models are:
public class ClassOne : Entity
{
public virtual string Title { get; set; }
public virtual ClassTwo[] Tags { get; set; }
}
public class ClassTwo : Entity
{
public virtual string Title { get; set; }
}
Base class contains the Id definition which is long in my case. Should not be a problem with Guids
Mapping class: We are using FluentNhibernate with some convention, also the idea is in HasManyToMany
public class ClassOneMappingOverride : IAutoMappingOverride<ClassOne>
{
public void Override(AutoMapping<ClassOne> mapping)
{
mapping.HasManyToMany(x => x.Tags).AsArray(x => x.Id).ParentKeyColumn("classOneId")
.ChildKeyColumn("classTwoId")
.Table("ClassOneLinkClassTwo")
.Cascade.SaveUpdate();
}
}
Please note that if you not indicate ParentKey, ChildKey and Table it will not create the link table.
The unit test which insert data looks like:
public class ClassOneDataPart : DataPartBase, IDataPart
{
public void AddToDatabase()
{
var classOne = new ClassOne { Title = "classOne" };
var classTwo1 = new ClassTwo { Title = "class21" };
var classTwo2 = new ClassTwo { Title = "class22" };
var tags = new[] { classTwo1, classTwo2 };
classOne.Tags = tags;
this.SaveData(classOne);
this.SaveData(classTwo1);
this.SaveData(classTwo2);
}
}
and the result into database is:
Regards,
Ion
Map the collection as a private field and expose it as an array. This also makes it easy to expose AddTag and RemoveTag methods without manipulating the array.
public class ClassOne
{
private IList<ClassTwo> _tags;
public virtual Guid Guid { get; set; }
public virtual String Title { get; set; }
public virtual ClassTwo[] Tags
{
// possibly expose as method to hint that the array is re-built on every call
get { return _tags.ToArray(); }
}
}
public class ClassOneMap : ClassMap<ClassOne>
{
public ClassOneMap ()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
HasManyToMany(x => x.Tags).Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate());
}
}
Try to use .AsArray(x=>x.Id)