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
Related
Ok, I am a little stumped on this NHibernate query. The confusion is around PasswordResetToken.
Firstly, here is the mapping:
public ContactMap()
{
Table("Contact");
Id(x => x.ContactId, "ContactId").Unique().GeneratedBy.Increment();
Map(x => x.EmailAddress);
...
Map(x => x.JobTitle);
References(x => x.PasswordResetToken, "EmailAddress")
.PropertyRef(x => x.EmailAddress)
.Cascade.None()
.Not.LazyLoad()
.Not.Update();
HasMany(x => x.Roles)
.Table("tblContactRole").KeyColumn("ContactId").Element("Role", part => part.Type<global::NHibernate.Type.EnumStringType<ContactRoles>>())
.AsSet()
.Not.LazyLoad();
}
Now here is the query:
public IList<Contact> GetContacts(int id)
{
var contacts = Session.CreateCriteria<Contact>()
.Add(Restrictions.Eq("Id", id))
.Add(Restrictions.Eq("IsActive", true))
.SetFetchMode("Roles", FetchMode.Eager)
.SetFetchMode("PasswordResetToken", FetchMode.Eager)
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity)
.List<Contact>();
return contacts;
}
My understanding is that FetchMode.Eager means a JOIN is used instead of a SUBSELECT, so there isn't any reason there for extra calls to the db to appear.
A correct SQL query is run returning all the information required to hydrate a Contact as evidenced from the screenshot from NHProf (the highlighted query) (dont' worry about different table names etc - I have sanitized the code above):
What I don't understand is why on earth dozens of separate selects to the PasswordResetToken table are generated and run?? One of these queries is only generated for every contact that doesn't have a PasswordResetToken (ie. the first query returns nulls for those columns) - not sure what this has to do with it.
A contact might or might not have a few roles (superfluous to this issue) and similarly, may or may not have exactly one PasswordResetToken.
The DB is a little dodgy with few foreign keys. The link between Contact and PasswordResetToken in this case is a simple shared column "EmailAddress".
All these queries are generated on the running of that single line of code above (ie. that code is not in a loop).
Let me know if I am missing any info.
What should I be googling?
It's a bug. I would try to get it to work with just two queries, although from the bug report it sounds like that will be a challenge.
The attached test shows that a many-to-one association referencing an unique property (instead of the Id) results in a select n+1 problem. Although the first statement contains the correct join, all associated entities are fetched one by one after the join select. (Entities with the same value in the unique column are even fetched more than once.)
The interesting point is that this bug only occurs if the referenced
entities are already in the session cache. If they are not, no
additional select statements are created.
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.
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?
Hey guys, I am having some real issues with mapping using fluent nhibernate. I realise there are MANY posts both on this site and many others focusing on specific types of mapping but as of yet, I have not found a solution that solves my issue.
Here is what I have:
namespace MyProject.Models.Entites
{
public class Project
{
public virtual Guid Id {get; set;}
// A load of other properties
public virtual ProjectCatagory Catagory{get;set;}
}
}
and then the map:
namespace MyProject.DataAccess.ClassMappings
{
public class ProjectMap : ClassMap<Project>
{
public ProjectMap()
{
Id(x => x.Id);
Map(x => x.Title);
Map(x => x.Description);
Map(x => x.LastUpdated);
Map(x => x.ImageData).CustomSqlType("image");
HasOne(x => x.Catagory);
}
}
}
So as you can see, I have a project which contains a catagory property. Im not so hot on relational databases but from what I can figure, this is a many-one relationship where many Projects can have one catagory. No, projects cannot fall into more than one category.
So now we have:
namespace MyProject.Models.Entities
{
public class ProjectCatagory
{
public virtual Guid Id { get; set; }
public virtual String Name { get; set; }
}
}
and its map:
public ProjectCatagoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
Issue is, well, it doesn't work ! I will do something similar to the following in a unit test:
Project myproject = new Project("Project Description");
// set the other properties
myProject.Catagory = new ProjectCatagory(Guid.New(), "Test Catagory");
repository.Save(myProject);
Now I have tried a number of mapping and database configurations when trying to get this to work. Currently, the Project database table has a column, "Catagory_id" (which i didnt put there, i assume NH added it as a result of the mapping) and I would LIKE it set to not allow nulls. However, when set as such, I get exceptions explaining that I cannot insert null values into the table (even though during a debug, i have checked all the properties on the Project object and they are NOT null).
Alternatively, I can allow the table to accept nulls into that column and it will simply save the Project object and totally disregard the Category property when saving, therefore, when being retrieved, tests to check if the right category has been associated with the project fails.
If i remember correctly, at one point I had the ProjectMap use:
References(x => x.Catagory).Column("Catagory_id").Cascade.All().Not.Nullable();
this changed the exception from "Cannot insert null values" to a foreign key violation.
I suspect the root of all this hassle comes from my lack of understanding of relational database set up as I have other entities in this project that do not have external dependencies which work absolutely fine with NHibernate, ruling out any coding issues I may of caused when creating the repository.
Any help greatly appreciated. Thank you.
The main issue here is a common misunderstand about the "one-to-one" relation in a relational database and the HasOne mapping in Fluent. The terms in the mapping are relational terms. (Fluent tries to "beautify" them a bit which makes it worse IMO. HasOne actually means: one-to-one.)
Take a look at the Fluent wiki:
HasOne is usually reserved for a
special case. Generally, you'd use a
References relationship in most
situations (see: I think you mean a
many-to-one).
The solution is very simple, just exchange HasOne with References (one-to-one to many-to-one in an XML mapping file). You get a foreign key in the database which references the ProjectCatagory.
A real one-to-one relation in a relational database is ideally mapped by a primary key synchronization. When two objects share the same primary key, then you don't waste space for additional foreign keys and it is ensured to be one-to-one.
To synchronize primary key, you need to hook up one's key to the others. However this works, it is not what you need here.
After playing around with all the available options for mapping. I found the answer to be similar to that suggested.
As was suspected, HasOne() was clearly wrong and References(x => x.Catagory) was part of the solution. However, I still received foreign key violation exceptions until:
References(x => x.Catagory).Column("Catagory_id").Cascade.SaveUpdate().Not.Nullable().Not.LazyLoad();
Just thought id update the thread in case someone else stumbles across this with a similar issue as just using References() did not work.
Its seems ProjectCatagory class is parent class of Project Class. So without parent class how can child class exist.
You have to use -
References(x => x.Catagory).Column("Catagory_id").Foreignkey("Id");
here Foreign Key is your ProjectCatagory table ID.
Whenever I load a Task class, the Document property is always null, despite there being data in the db.
Task class:
public class Task
{
public virtual Document Document { get; set; }
Task Mapping override for AutoPersistenceModel:
public void Override(AutoMap<Task> mapping)
{
mapping.HasOne(x => x.Document)
.WithForeignKey("Task_Id");
As you can see form what NHProf says is being run, the join condition is wrong, the WithForeignKey doesnt seem to take effect. In fact, i can write any string in the above code and it makes no difference.
FROM [Task] this_
left outer join [Document] document2_
on this_.Id = document2_.Id
It should be:
FROM [Task] this_
left outer join [Document] document2_
on this_.Id = document2_.Task_Id
If i hack the data in the db so that the ids match, then data is loaded, but obviously this is incorrect - but at least it proves it loads data.
Edit: rummaging in the fluent nhib source to find the XML produces this:
<one-to-one foreign-key="Task_Id" cascade="all" name="Document" class="MyProject.Document, MyProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
Edit: heres the schema:
CREATE TABLE [dbo].[Document](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Task_Id] [int] NOT NULL,
CREATE TABLE [dbo].[Task](
[Id] [int] IDENTITY(1,1) NOT NULL,
Anyone got any ideas?
Thanks
Andrew
I ran into the same issue today. I believe the trick is not to use .ForeignKey(...) with the .HasOne mapping, but to use .PropertyRef(...) instead. The following is how I define a One-to-one relationship between an Organisation (Parent) and its Admin (Child):
HasOne(x => x.Admin).PropertyRef(r => r.Organisation).Cascade.All();
The Admin has a simple reference to the Organisation using its Foreign Key:
References(x => x.Organisation, "ORAD_FK_ORGANISATION").Not.Nullable();
When retrieving an Organisation, this will load up the correct Admin record, and properly cascades updates and deletes.
You should use:
References(x => x.Document, "DocumentIdColumnOnTask")
I think the problem here is that the "HasOne" convention means that you are pointing at the other thing(the standard relational way to say "Many To One"/"One to One"); By putting a Task_ID on the document the actual relationship is a HasMany but you have some kind of implicit understanding that there will only be one document per task.
Sorry - I don't know how to fix this, but I will be interested in seeing what the solution is (I don't use NHibernate or Fluent NHibernate, but I have been researching it to use in the future). A solution (from someone with very little idea) would be to make Documents a collection on Task, and then provide a Document property that returns the first one in the collection (using an interface that hides the Documents property so no one thinks they can add new items to it).
Looking through documentation and considering eulerfx's answer, Perhaps the approach would be something like:
References(x => x.Document)
.TheColumnNameIs("ID")
.PropertyRef(d => d.Task_ID);
EDIT: Just so this answer has the appropriate solution: The correct path is to update the database schema to match the intent of the code. That means adding a DocumentID to the Task table, so there is a Many-To-One relationship between Task and Document. If schema changes were not possible, References() would be the appropriate resolution.
I've tried this solution:
just in Document:
mapping.HasOne(x => x.Task).ForeignKey("Task_ID").Constrained().Cascade.All();
As eulerfx pointed out,
the table structure indicates that there maybe mulitple documents for a task
and Chris stated:
By putting a Task_ID on the document the actual relationship is a HasMany but you have some kind of implicit understanding that there will only be one document per task.
This is of course correct so I have reversed it so Task has a nullable Document_Id.
Thanks to both of you for you help!
I flipped a coin for the accepted answer, if i could tick both i would!
I have been struggling with the same Has One problem and finally found that this worked:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.Id);
HasOne(s => s.Child).Cascade.All();
}
}
public class ChildMap : ClassMap<Model.Child>
{
public ChildMap()
{
Id(x => x.Id);
HasOne(s => s.Parent).Constrained().ForeignKey();
}
}