I have the following use case:
public class Object {
long Id
...
ISet<Tags> tags
}
public class Tag : IEquatable<Tag> {
string Label;
}
Object is an Aggregate Root and Tag a Value Object.
Both are stored in 2 different tables:
CREATE TABLE incident(id bigint, ...)
CREATE Table tag (object_id bigint References object(id), label varchar,...)
I'm trying to create the ClassMap using FluentNhibernate, which works well for object but I couldn't find a way to map it with Tag
public ObjectsMapping()
{
Id(x => x.Id).GeneratedBy.Assigned();
Version(x => x.ObjectVersion).Column("object_version");
HasMany(x => x.Tags).Inverse().Cascade.All().KeyColumn("object_id");
}
public TagsMapping()
{
CompositeId().KeyProperty(x => x.Label).KeyProperty(x => x.CreationTimestamp);
Map(x => x.Label);
Map(x => x.CreationTimestamp);
}
Any idea how to map that an entity that has a oneToMany relation with a ValueObject from another table ?
Basically I'm looking for an equivalient of Set() in NHibernate
Thank you
I found the solution:
In ObjectMapping:
HasMany(x => x.Tags).Component(x => { x.Map(k => k.Label); }).Table("tag");
Related
I'm trying to map a couple of tables where the primary key in Bar is a foreign key to Foo i.e. a 1..0:1 relationship.
My mappings look like this:
class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Table("Foo");
Id(x => x.Id).Column("ID");
HasOne(x => x.Bar).Cascade.All();
}
}
class BarMapping : ClassMap<Bar>
{
public BarMapping()
{
Table("Bar");
Id(x => x.FooId).GeneratedBy.Foreign("Foo");
HasOne(x => x.Foo).Constrained();
}
}
The problem is that when I try to remove an instance of Bar by setting Foo.Bar = null and Bar.Foo = null, the record does not get deleted from the database.
What am I missing?
So basicly the problem is that you're using Cascade.All() in the mapping of Foo.
Instead of that you should be using Cascade.AllDeleteOrphan().
Cascade.All
when an object is save/update/delete, check the associations and save/update/delete all the objects found.
Cascade.AllDeleteOrphan
when an object is save/update/delete, check the associations and save/update/delete all the objects found. In additional to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
There is other dirty solution by making this relation with using HasMany and Cascade.AllDeleteOrphan.
class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Table("Foo");
Id(x => x.Id).Column("ID");
HasMany(x => x.Bar).KeyColumn("FooId").Cascade.AllDeleteOrphan();
}
}
I have a many to many relationship between
Portfolio and PortfolioTags
A portfolio Item can have many PortfolioTags
I am looking at the best way of saving tags to a portfolio item. My Nhibnerate maps are like so:
public class PortfolioMap : ClassMap<Portfolio> {
public PortfolioMap() {
Table("Portfolio");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.AliasTitle).Column("AliasTitle").Not.Nullable();
Map(x => x.MetaDescription).Column("MetaDescription").Not.Nullable();
Map(x => x.Title).Column("Title").Not.Nullable();
Map(x => x.Client).Column("Client").Not.Nullable();
Map(x => x.Summary).Column("Summary").Not.Nullable();
Map(x => x.Url).Column("Url");
Map(x => x.MainImage).Column("MainImage");
Map(x => x.TitleAlt).Column("TitleAlt");
Map(x => x.Description).Column("Description").Not.Nullable();
HasMany(x => x.PortfolioImage).KeyColumn("PortfolioId").Inverse();
HasMany(x => x.PortfolioTag).KeyColumn("PortfolioId").Cascade.All().Table("PortfolioTag").Inverse();
}
}
public class PortfoliotagMap : ClassMap<Portfoliotag> {
public PortfoliotagMap() {
Table("PortfolioTag");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
References(x => x.Portfolio).Column("PortfolioId");
References(x => x.Tag).Column("TagId");
}
}
public class TagMap : ClassMap<Tag>
{
public TagMap() {
Table("Tag");
LazyLoad();
Id(x => x.TagId).GeneratedBy.Identity().Column("TagId");
Map(x => x.TagVal).Column("Tag").Not.Nullable();
HasManyToMany(x => x.Portfolio).Table("PortfolioTag").ParentKeyColumn("TagId").ChildKeyColumn("PortfolioId").Inverse();
}
}
In my portfolio controller I am first trying to save my tags that do not exist. I tried using SaveOrUpdate on a tag repository. However as the ids are different multiple save of tags occurs at this point.
I thought about the following steps but it seems long winded:
1) getting all tags:
var tags = _tagRepository.GetAll();
2) Iterating over the tags from the item to save and seeing if they exist in the database. If so I would need to get the tag and associate with the portfolio item. If not i would need to save the tag one by one and then associate with the portfolio item.
var tags = _tagRepository.GetAll();
foreach (var tagInPortfolio in StringUtilities.SplitToList(model.Tags, new[] { ',' }))
{
// tag does not exist so save it
if (tags.Any(i => i.TagVal == tagInPortfolio))
{
_tagRepository.SaveOrUpdate(new Tag {TagVal = tagInPortfolio});
}
}
3) I then need to delete any relationships i.e. tags to portfolio items that dont exist.
4) Finally need to add the tag to to the portfolioTag. I would need to get all the tags again and then associate:
portfolio.PortfolioTag.Add(new Portfoliotag {Portfolio = portfolio, Tag = tag});
_portfolioRepository.UpdateCommit(portfolio);
This seems to long winded. Can anyone explain the most simplest way of doing this please.
I have looked at saveandcommit on tags but i get multiple inserts because of ids being different. Do I need to delete all existing tag relationships also as this seems to much logic for something simple.
Create now works with a commit -
public void CreateCommit(T entity)
{
using (ITransaction transaction = Session.BeginTransaction())
{
Session.Save(entity);
transaction.Commit();
}
}
However using the below and the above maps still meant duplicates where occurring in the tag table. So if one portfolio record added a tag like abc and another portfolio record added a tag abc i need the join table to reference the same record in the tag and not create another instance of abc. Do i need to do a lookup on the tag table to avoid this
public void UpdateCommit(T entity)
{
using (ITransaction transaction = Session.BeginTransaction())
{
Session.Update(entity);
transaction.Commit();
}
}
If I understood correctly, I think you misunderstood the many-to-many mapping. If you really have a relationship like this between the Portifolio and the Tag classes, you should not map the PortfolioTag table.
In a simple many-to-many relationship the table used to connect the other two main tables should have only the foreign keys from the two tables (that would also be a composite key of this intermediate table). In this case, the PortfolioTag table would have only two columns: PortfolioId and TagId, that would be not only foreign keys for the Portfolio and Tag tables, but also the primary key of this intermediate table.
In this case, your Portfolio class should have a list of Tags instead of a list of PortfolioTag, and the Tag class a list of Portfolios. And you should map the Portfolio and the Tag like this (with no need of mapping the intermediate table):
public class PortfolioMap : ClassMap<Portfolio> {
public PortfolioMap() {
Table("Portfolio");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
//
// Code intentionally omitted for clarity
//
HasManyToMany(x => x.Tags)
.Table("PortfolioTag")
.ParentKeyColumn("PortfolioId")
.ChildKeyColumn("TagId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class TagMap : ClassMap<Tag> {
public TagMap() {
Table("Tag");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.TagVal).Column("Tag").Not.Nullable();
HasManyToMany(x => x.Portfolios)
.Table("PortfolioTag")
.ParentKeyColumn("TagId")
.ChildKeyColumn("PortfolioId")
.Inverse();
}
}
You will also have to save the Portfolio inside the context of a transaction for the intermediate table to be populated, like bellow:
using (var trans = Session.BeginTransaction()) {
try {
Session.SaveOrUpdate(obj);
trans.Commit();
} catch {
trans.Rollback();
throw;
}
}
//or like this (with TransactionScope)
using (var trans = new TransactionScope()) {
Session.SaveOrUpdate(obj);
trans.Complete();
}
With this approach, you would also be excused of the need to iterate through the Tags list to save each one. You would be able to just save the Portfolio and everything should be fine.
P.S.: I tested this code with FluentNHibernate v1.4.0.0 and NHibernate v3.3.1.4000.
I have an entity:
public class Foo
{
public virtual int Id;
public virtual IEnumberable<string> Bars;
}
And its mapping:
public class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Table("foo_table_in_database");
Id(x => x.Id, "Id");
HasMany(x => x.Bars)
.AsList()
.Table("bars_table_in_db")
.Element("BarId", m =>
{
m.Type<string>();
});
}
}
And an exception returned inside the entity insteaf of the expected result :(
base = {"could not initialize a collection: [Loya.Services.CouponsWeb.Promotion.LoyCouponCustomerGroups#2][SQL: SELECT loycouponc0_.Promotion_id as Promotion3_0_, loycouponc0_.LoyCustomerGroupId as LoyCusto1_0_, loycouponc0_.Index as Index0_ FROM loy_promotion__cu...
Database tables :
foo_table : *Id, other properties
bar_table : *FooId, *BarId
My aim is to get a List of BarId's (strings) in my Foo.
How do I map it properly?
I think you might need to specify the KeyColumn. I do something similar in one of my solutions and I would map the entities above as follows...
mapping.HasMany(x => x.Bars)
.Schema("Schema")
.Table("FooBars")
.Element("Bar")
.KeyColumn("FooID")
.ForeignKeyConstraintName("FK_FooBar_Foo")
.Not.Inverse()
.Cascade.All()
.Fetch.Select();
This will give a table called Schema.FooBars. It will have a FooID column (which is a foreign key back to the Foo table) and a Bar column which contains the value in your Bars collection.
I have 2 tables like this: (Please note the non-standard db schema naming)
table T_Pen
TP_ID
TP_PrimaryColorID
TP_SecondaryColorID
...
table E_Color
EC_ID
EC_ColorName
...
And I want to create a mapping of the 2 tables to a domain object Pen using Fluent NHibernate.
class Pen
{
PenID;
PrimaryColorName;
SecondaryColorName;
...
}
How can I do that?
I don't think you would be able to Insert/Update anymore if you were to only reference the Name.
You could create a view of PenColour or hide the actual reference in your pen class and only expose the Name property.
class Pen
{
int PenID;
Color PrimaryColor;
Color SecondaryColor;
}
class Color
{
int ColorID;
string ColorName;
}
class ColorMap
{
Id(x => x.ColorID);
Map(x => x.ColorName);
}
class PenMap
{
Id(x => x.PenID);
References(x => x.PrimaryColor).Column("TP_PrimaryColorID");
References(x => x.SecondaryColor).Column("TP_SecondaryColorID");
}
this is my db:
tblGroups:
GroupID
GroupName
tblMembersInGroup
GroupID
MemberID
Rank
tblMembers
MemberID
Name
this is my Model:
class group
{
int groupid
string name
List<EnlistedMembers> myMembers;
}
class EnlistedMebmers
{
Member myMember;
int Rank;
}
class Member
{
int MemberID
string Name
}
I am not sure how to map this in FNH, as the Members class has no object that tells FNH who is the father.
I think the group mapping is obvious:
Table(tblGroup);
Id(x => x.groupid, "GroupID').GeneratedBy.Identity();
Map(x => x.name, "GroupName");
HasMany(x => x.myMembers).AsList().Inverse().Cascade.All();
What's next ?
how do I map enlistedmembers, with no identity object? and no group object ?
but with the extra rank data. the db has all the data, I need a way to create the classes right...
Thanks,
Dani
Is there a reason you need to map EnlistedMembers? If not, you could map Group.MyMembers like this:
HasManyToMany(z => z.MyMembers)
.AsList()
.Cascade.All()
.Table("tblMembersInGroup")
.ParentKeyColumn("GroupId")
.ChildKeyColumn("MemberId");
I suspect this may be a square peg for a round hole, but I'd look into SubClassing.
public class MemberMap : ClassMap<Member>
{
public MemberMap()
{
Table("tblMembers");
Id(x => x.MemberID);
Map(x => x.Name);
}
}
public class EnlistedMembersMap : SubclassMap<EnlistedMembers>
{
public EnlistedMembersMap()
{
Table("tblMembersInGroup");
Map(x => x.Rank);
}
}
You'll need to make EnlistedMembers inherit from Member. I'm also pretty sure it's going to bark about the composite key you have going in tblMembersInGroup. I don't have an answer for that... yet.