Say I have three tables Products, Languages and ProductTranslations that look like the following:
(1) ProductId
(1) StandardName
(1) Price
(2) LanguageId
(2) LanguageName
and
(3) ProductTranslationId
(3) ProductId
(3) LanguageId
(3) LocalName
(3) LocalDescription
In my object model, I need a single object (single class map) that contains the following
(3) ProductTranslationId
(2) Language
(3) LocalName
(3) LocalDescription
(1) Price
This object is only used for display, so read-only (immutable) is fine.
ProductTranslationId uniquely identifies a row.
If necessary, this object can include the LanguageID and ProductID values as well. the important feature is that this needs to include the Language and Price values from the ONE side of this one to many relationship.
I can see how to do this in 3 separate classes using Many-to-One, but how can I do this with one class, where ProductTranslationId is the <id> value?
Is there an alternative that does not require creating a view in the database?
Option 1
// FluentNHibernate Mapping
public class ProductTranslationDtoMap : ClassMap<ProductTranslationDto>
{
public ProductTranslationDtoMap()
{
ReadOnly();
Table(" ProductTranslation");
Id(x => x.Id, "ProductTranslationId");
Map(x => x.Language).Formula("(SELECT l.LanguageName FROM Language l WHERE l.LanguageId = LanguageId)"); // simple and highly portable sql formula
Map(x => x.LocalName);
Map(x => x.LocalDescription);
Map(x => x.Price).Formula("(SELECT p.Price FROM Products p WHERE p.ProductId = ProductId)");
}
}
Option 2
Product prod = null;
Language lang = null;
TranslationDto alias = null;
var results = session.QueryOver<ProductTranslation>()
.JoinAlias(pt => pt.Language, () => lang)
.JoinAlias(pt => pt.Product, () => prod)
.SelectList(list => list
.Select(pt => pt.Id).WithAlias(() => alias.ProductTranslationId)
.Select(() => lang.Name).WithAlias(() => alias.Language)
...)
.TransformUsing(Transformers.AliasToBean<TranslationDto>())
.List<TranslationDto>();
Instead of using a NHibernate mapping you could also use a class mapper like AutoMapper to create a flat representation of your complex object graph. The advantage would be one nhibernate mapping less to maintain (although the fluent variant from Firo offers compiler syntax checking) in case your db schema changes. The disadvantage of course would be speed, as you first have to make nhibernate create your complex object graph, then transform that graph into a flat object. If you only need this one flat object, you may be better of with a single NH mapping. If you think about creating several more views, a class mapper might be worth a look.
Related
I'm trying to fetch a collection of read-only objects from NHibernate where all the properties come from a single table (Answers), with the exception of one property that comes from another table (Questions) in a many to one relationship. The fact it's two tables is an implementation detail that I want to hide, so I want the repository to return a sensible aggregate. The trouble is this requires I have two classes, one for each table, that NHibernate returns and then I have to select/map that into a third class that my repository returns. This feels a bit rubbish so instead I was hoping to have a mapping that joins the two tables for me but maps all the columns onto a single class. My mapping looks like this:
public QuestionAnswerMap()
{
ReadOnly();
Table("Question");
Id(x => x.Id).Column("questionId").GeneratedBy.Identity();
Map(x => x.AnswerShortCode).Column("AnswerShortCode");
Join("Answers", join =>
{
join.Fetch.Join();
join.KeyColumn("questionId").Inverse();
join.Map(x => x.QuestionId).Column("QuestionId");
join.Map(x => x.AnswerId).Column("AnswerId");
join.Map(x => x.MemberId).Column("MemberId");
});
}
The SQL this generates looks perfect and returns exactly what I want, but when there are multiple Answers that join to the same row in the Questions table, NHibernate seems to map them to objects wrongly - I get the right number of results, but all the Answers that have a common Question are hydrated with the first row in sql result for that Question.
Am I doing this the right way? NH is generating the right SQL, so why is it building my objects wrong?
because Join was meant to be this way. It assumes a one to one association between the two tables which are not the case.
Instead of a mapped Entity i would prefere a on the fly Dto for this:
var query = session.Query<Answer>()
.Where(answer => ...)
.Select(answer => new QuestionAnswer
{
QuestionId = answer.Question.Id,
AnswerShortCode = answer.Question.AnswerShortCode,
AnswerId = answer.Id,
MemberId = answer.MemberId,
});
return query.ToList();
I'm dealing with a database schema that is err... less than ideal. The domain deals with courses. The courses have prerequisites and related courses. The db model is somthing like this:
Courses
courseid -int
code -varchar
related_courses
part_number varchar
related_part_number varchar
type int
As you might have guessed, the course and related course table is not connected vie the course pk, but instead the code column. Then the type of relationship is defined by the type column in related_course.
I would love to have a list of prerequisites and a list of related courses in my course object,but I have been unsucessfully in doing so. I'm now trying to just match the course with the related items and then filter on the type. That is not working either.
Here is my current mapping for course and course_related.
public CourseMap()
{
Map(x => x.Code);
HasMany(x => x.RelatedItems)
.Access.Property()
.PropertyRef("Code")
.KeyColumn("Part_Id");
}
public CourseRelatedMap()
{
References(x => x.Course, "part_id");
HasMany(x => x.RelatedCourses)
.Access.Property()
.KeyColumn("part_related_id");
//.PropertyRef("part_related_id");
}
When I try to query for the related courses, this is generating the correct sql for me:
SELECT relatedite0_.Part_Id as Part2_1_,
relatedite0_.CourseRelatedId as CourseRe1_1_,
relatedite0_.CourseRelatedId as CourseRe1_12_0_,
relatedite0_.part_id as part2_12_0_,
relatedite0_.Type as Type12_0_
FROM OCT_Course_Related relatedite0_
WHERE relatedite0_.Part_Id = '1632LGEE-ILT' /* #p0 */
But NH is throwing an error trying to convert a string to int, so I'm guessing that it's trying to convert relatedite0_.Part_Id = '1632LGEE-ILT' /* #p0 */ to an integer.
Any help with this would be greatly appreciated,
Try mapping Identity properties to your entities.
For CourseMap Something like this
Id(x => x.Id).Column("courseid")
I don't know if this will do it as I'm not sure I fully understood your model
i have readonly VIEWs in an existing Database and i'd like to get them with FHN. i tried mapping it the following way:
public class HhstMap : ClassMap<Hhst>
{
public HhstMap()
{
Table("HHST");
ReadOnly();
Id();
Map(x => x.Hkz);
Map(x => x.Kapitel);
Map(x => x.Titel);
Map(x => x.Apl);
Map(x => x.Hhpz);
}
}
but i got an error:
could not execute query
[ SELECT this_.id as id3_0_, this_.Hkz as Hkz3_0_, this_.Kapitel as Kapitel3_0_, this_.Titel as Titel3_0_, this_.Apl as Apl3_0_, this_.Hhpz as Hhpz3_0_ FROM HHST this_ ]
this is ok cause there is no ID Column, but how can i do mapping with Fluent without the ID?
You could retrieve the records as value objects (non-managed entities) instead of entities.
"14.1.5. Returning non-managed entities
It is possible to apply an IResultTransformer to native sql queries. Allowing it to e.g. return non-managed entities.
sess.CreateSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.SetResultTransformer(Transformers.AliasToBean(typeof(CatDTO)))
This query specified:
the SQL query string
a result transformer
The above query will return a list of CatDTO which has been instantiated and injected the values of NAME and BIRTHNAME into its corresponding properties or fields. "
Maybe this can help you: Fluent nHibernate no identity column in tableā¦?
EDIT:
Also, you could use a composite id, or maybe you need the latest version of Fluent Nhibernate?
One thing with which I have long had problems, within the CakePHP framework, is defining simultaneous hasOne and hasMany relationships between two models. For example:
BlogEntry hasMany Comment
BlogEntry hasOne MostRecentComment (where MostRecentComment is the Comment with the most recent created field)
Defining these relationships in the BlogEntry model properties is problematic. CakePHP's ORM implements a has-one relationship as an INNER JOIN, so as soon as there is more than one Comment, BlogEntry::find('all') calls return multiple results per BlogEntry.
I've worked around these situations in the past in a few ways:
Using a model callback (or, sometimes, even in the controller or view!), I've simulated a MostRecentComment with:
$this->data['MostRecentComment'] = $this->data['Comment'][0];
This gets ugly fast if, say, I need to order the Comments any way other than by Comment.created. It also doesn't Cake's in-built pagination features to sort by MostRecentComment fields (e.g. sort BlogEntry results reverse-chronologically by MostRecentComment.created.
Maintaining an additional foreign key, BlogEntry.most_recent_comment_id. This is annoying to maintain, and breaks Cake's ORM: the implication is BlogEntry belongsTo MostRecentComment. It works, but just looks...wrong.
These solutions left much to be desired, so I sat down with this problem the other day, and worked on a better solution. I've posted my eventual solution below, but I'd be thrilled (and maybe just a little mortified) to discover there is some mind-blowingly simple solution that has escaped me this whole time. Or any other solution that meets my criteria:
it must be able to sort by MostRecentComment fields at the Model::find level (ie. not just a massage of the results);
it shouldn't require additional fields in the comments or blog_entries tables;
it should respect the 'spirit' of the CakePHP ORM.
(I'm also not sure the title of this question is as concise/informative as it could be.)
The solution I developed is the following:
class BlogEntry extends AppModel
{
var $hasMany = array( 'Comment' );
function beforeFind( $queryData )
{
$this->_bindMostRecentComment();
return $queryData;
}
function _bindMostRecentComment()
{
if ( isset($this->hasOne['MostRecentComment'])) { return; }
$dbo = $this->Comment->getDatasource();
$subQuery = String::insert("`MostRecentComment`.`id` = (:q)", array(
'q'=>$dbo->buildStatement(array(
'fields' => array( String::insert(':sqInnerComment:eq.:sqid:eq', array('sq'=>$dbo->startQuote, 'eq'=>$dbo->endQuote))),
'table' => $dbo->fullTableName($this->Comment),
'alias' => 'InnerComment',
'limit' => 1,
'order' => array('InnerComment.created'=>'DESC'),
'group' => null,
'conditions' => array(
'InnerComment.blog_entry_id = BlogEntry.id'
)
), $this->Comment)
));
$this->bindModel(array('hasOne'=>array(
'MostRecentComment'=>array(
'className' => 'Comment',
'conditions' => array( $subQuery )
)
)),false);
return;
}
// Other model stuff
}
The notion is simple. The _bindMostRecentComment method defines a fairly standard has-one association, using a sub-query in the association conditions to ensure only the most-recent Comment is joined to BlogEntry queries. The method itself is invoked just before any Model::find() calls, the MostRecentComment of each BlogEntry can be filtered or sorted against.
I realise it's possible to define this association in the hasOne class member, but I'd have to write a bunch of raw SQL, which gives me pause.
I'd have preferred to call _bindMostRecentComment from the BlogEntry's constructor, but the Model::bindModel() param that (per the documentation) makes a binding permanent doesn't appear to work, so the binding has to be done in the beforeFind callback.
I have a existing parent-child relationship I am trying to map in Fluent Nhibernate:
[RatingCollection] --> [Rating]
Rating Collection has:
ID (database generated ID)
Code
Name
Rating has:
ID (database generated id)
Rating Collection ID
Code
Name
I have been trying to figure out which permutation of HasMany makes sense here. What I have right now:
HasMany<Rating>(x => x.Ratings)
.WithTableName("Rating")
.KeyColumnNames.Add("RatingCollectionId")
.Component(c => { c.Map(x => x.Code);
c.Map(x => x.Name); );
It works from a CRUD perspective but because it's a bag it ends up deleting the rating contents any time I try to do a simple update / insert to the Ratings property. What I want is an indexed collection but not using the database generated ID (which is in the six digit range right now).
Any thoughts on how I could get a zero-based indexed collection (so I can go entity.Ratings[0].Name = "foo") which would allow me to modify the collection without deleting/reinserting it all when persisting?
First of all, you're using an old version of Fluent NHibernate; WithTableName has been deprecated in favor of Table.
An IList can be accessed by index so a Bag should work. I'm not sure why you have mapped Rating as a Component or what the effect of that is. This is a standard collection mapping:
HasMany(x => x.Ratings).KeyColumn("RatingCollectionId")
.Cascade.AllDeleteOrphan().Inverse()
.AsBag().LazyLoad();