This question already has answers here:
Fluent nHibernate: one-to-many relationship Issue
(2 answers)
Closed 9 years ago.
I have simple one-to-many relationship
public class Product
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Category { get; set; }
public virtual bool Discontinued { get; set; }
public virtual IList<ProductCampaignTargeting> Targetings { get; set; }
}
public class ProductCampaignTargeting
{
public virtual Guid ID { get; set; }
public virtual int TargetType { get; set; }
public virtual string TargetValue { get; set; }
public virtual Product Product { get; set; }
}
with Mapping:
class ProductCampaignTargetingMap : ClassMap<ProductCampaignTargeting>
{
public ProductCampaignTargetingMap()
{
Table("campaign_targetings");
Id(x => x.ID).GeneratedBy.Guid();
Map(x => x.TargetType).Column("target_type");
Map(x => x.TargetValue).Column("target_value");
References(x => x.Product).Column("campaign_id_fk");
}
}
class ProductMap: ClassMap<Product>
{
public ProductMap()
{
Table("Product");
Id(x => x.Id).Column("id").GeneratedBy.Guid();
Map(x => x.Name).Column("Name");
Map(x => x.Category);
// check why inverse doens't work
HasMany(x => x.Targetings).KeyColumn("campaign_id_fk").Cascade.AllDeleteOrphan().AsBag();
}
}
and it is working - but the child (many) table is updated with two commands - insert and then update
When i want to change it to one command I use the Inverse() option - but then The Foreign key is populated as null, what am i missing here?
The foreign key is null when you use Inverse because you have to maintain both sides of the relationship when adding a ProductCampaignTargeting to the collection:
target.Product = product;
product.Targetings.Add(target);
This is one of the reasons that many NHibernate developers recommend exposing the collection as IEnumerable or read-only and encapsulating the Add and Remove methods:
void AddTarget(ProductCampaignTargeting target)
{
target.Product = this;
Targetings.Add(target);
}
Related
I need to create a many to many circular relation with two objects in fluent api. And I don't know how to use fluent api to achieve that goal.
The relation should be one to many (required) and one to many (optionnel)
I tried this scenario and it's not working.
Here my class model:
[Table("Question")]
public class Question
{
public int ID { get; set; }
public int IDQuestion { get; set; }
public int IDForm { get; set; }
public virtual AnswerSelectionType AssociateAnswerSelectionType { get; set; }
public virtual ICollection<AnswerSelectionType> AnswerSelectionTypes { get; set; }
}
[Table("AnswerSelectionType")]
public class AnswerSelectionType
{
public int ID { get; set; }
public int IDQuestionForm { get; set; }
public int? IDAssociateQuestion { get; set; }
public int? DataValue { get; set; }
public virtual Question AssociateQuestion { get; set; }
public virtual Question QuestionForm { get; set; }
}
After that, here the fluent API
modelBuilder.Entity<AnswerSelectionType>()
.HasRequired(choixRep => choixRep.QuestionForm)
.WithMany(questionTe => questionTe.AnswerSelectionTypes)
.HasForeignKey(fk => fk.IDQuestionForm);
modelBuilder.Entity<AnswerSelectionType>()
.HasOptional(choixRep => choixRep.AssociateQuestion)
.WithMany(many => many.AnswerSelectionTypes)
.HasForeignKey(fk => fk.IDAssociateQuestion);
The error I got is: The navigation property declared on type has been configured with conflicting multiplicities.
Could you help me to find why I got the error?
Thank you very much!!
AnswerSelectionType would need to have a collection of the entity on the many side. I don't see any collection in AnswerSelectionType.
But I'm not sure what you mean by circular. What is the structure you have in mind?
Question --> 1:n --> AnswerSelectionType --> 1:n --> Question
In this case a property in AnswerSelectionType would help.
ICollection<Question> Questions { get; set; }
And then
.WithMany(many => many.Questions)
(Note, remove "NHEntity" from the text below)
Try this:
public class AnswerSelectionTypeNHEntityMapping : ClassMap<AnswerSelectionTypeNHEntity>
{
public AnswerSelectionTypeNHEntityMapping()
{
Schema("dbo");
Table("AnswerSelectionTypeNHEntity");
Id(x => x.AnswerSelectionTypeNHEntityKey).GeneratedBy.Increment();
Map(x => x.IDQuestionForm);
Map(x => x.IDAssociateQuestion).Not.Nullable();
Map(x => x.DataValue).Not.Nullable();
References<QuestionNHEntity>(x => x.QuestionForm)
.Class(typeof(QuestionNHEntity))
/*.Not.Nullable() */
.Nullable()
.Column("ParentQuestionFormKey")
.Index("IX_AnswerSelectionType_ParentQuestionFormKey")
.Cascade.SaveUpdate()
;
References<QuestionNHEntity>(x => x.AssociateQuestion )
.Class(typeof(QuestionNHEntity))
/*.Not.Nullable() */
.Nullable()
.Column("OneToOneAssociateQuestionKey")
.Index("IX_AnswerSelectionType_OneToOneAssociateQuestionKeyy")
.Cascade.SaveUpdate()
;
}
}
public class QuestionNHEntityMapping : ClassMap<QuestionNHEntity>
{
public QuestionNHEntityMapping()
{
Schema("dbo");
Table("QuestionNHEntity");
Id(x => x.QuestionNHEntityKey).GeneratedBy.Assigned();
Map(x => x.IDQuestion).Not.Nullable().Index("IX_Question_IDQuestion"); ;
Map(x => x.IDForm).Not.Nullable();
HasMany<AnswerSelectionTypeNHEntity>(x => x.AnswerSelectionTypes)
.Inverse()
.KeyColumns.Add("MyAnswerSelectionTypesColumnName")
;
References<AnswerSelectionTypeNHEntity>(x => x.AssociateAnswerSelectionType)
.Class(typeof(AnswerSelectionTypeNHEntity))
/*.Not.Nullable() */
.Nullable()
.Column("OneToOneAnswerSelectionTypeNHEntityKey")
.Index("IX_AnswerSelectionType_OneToOneAnswerSelectionTypeNHEntityKey")
.Cascade.SaveUpdate()
;
}
}
I am working to map an existing database using Fluent NHibernate and have encountered a problem when it comes to complex many-to-many relationships (additional columns).
I know that many-to-many relationships with additional columns have to be mapped as HasMany rather than HasManyToMany as they are not pure many-to-many relationships. The linking table has to be mapped as a class within itself, which I have done in the example below.
When loading this data from an existing database it loads fine. The project I am working on takes this data and inserts it into an empty database, which is where the problem occurs. I think that when inserting into the new database the CompositeId is trying to insert NULL values for ItemID and ItemGroupID which is not allowed in the database. Changing the database structure is not a viable option at this point, is there a way around this issue?
Thanks, example code below.
Entity Classes
public class Item
{
public virtual long ItemID { get; set; }
public virtual string Name { get; set; }
}
public class ItemGroup
{
public virtual long ItemGroupID { get; set; }
public virtual string Name { get; set; }
public virtual IList<ItemInGroup> ItemsInGroup { get; set; }
}
public class ItemInGroup
{
public virtual Item Item { get; set; }
public virtual ItemGroup ItemGroup { get; set; }
public virtual int? DisplayOrder { get; set; }
}
Mapping Classes
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
Table("Items");
Id(x => x.ItemID).GeneratedBy.Identity();
Map(x => x.Name);
}
}
public class ItemGroupMap : ClassMap<ItemGroup>
{
public ItemGroupMap()
{
Table("ItemGroups");
Id(x => x.ItemGroupID).GeneratedBy.Identity();
Map(x => x.Name);
HasMany(x => x.ItemsInGroup).KeyColumn("ItemGroupID").Cascade.All();
}
}
public class ItemInGroupMap : ClassMap<ItemInGroup>
{
public ItemInGroupMap()
{
Table("ItemsInGroups");
CompositeId().KeyReference(x => x.Item, "ItemID")
.KeyReference(x => x.ItemGroup, "ItemGroupID");
Map(x => x.DisplayOrder);
}
}
assuming DisplayOrder is the only extra column in the link table why not use the List index of IList as order?
public class ItemGroup
{
public virtual long ItemGroupID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Item> Items { get; private set; }
}
public class ItemGroupMap : ClassMap<ItemGroup>
{
public ItemGroupMap()
{
Table("ItemGroups");
Id(x => x.ItemGroupID).GeneratedBy.Identity();
Map(x => x.Name);
HasManyToMany(x => x.ItemsInGroup)
.Table("ItemsInGroups")
.ParentKeyColumn("ItemGroupID")
.ChildKeyColumn("ItemID")
.AsList("DisplayOrder")
.Cascade.All();
}
}
I have 2 master tables which are linked by a map table as below
User [UserId,Name]
Resource [ResourceId,Name]
UserResourceMap [UserId,ResourceId,AccessLevel]
How would the Resource and User ClassMap with AccessLevel as a resource attribute look?
My Domain classes look like this
public class User
{
public virtual int UserId { get;protected set; }
public virtual string Name { get;set; }
}
public class Resource
{
public virtual int ResourceId { get;protected set; }
public virtual string Name { get;set; }
public virtual string AccessLevel { get;set; }//Issue-populate this using fluent
}
How can I use fluent to map the accessLevel attribute in the below code.
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("User");
Id(x => x.Key);
Map(x=>x.Name);
}
}
public class ResourceMap : ClassMap<Resource>
{
public ResourceMap()
{
Table("Resource");
Id(x => x.Key);
Map(x=>x.Name);//Need some Map Here to make a hasManyToMany Map with attribute
}
}
Your domain model does not seem to match your database model - the Resource class has the property AccessLevel (i.e. one AccessLevel per Resource) but in the DB model AccessLevel is a column on the map table (i.e. one AccessLevel per User-Resource relation).
Assuming the DB model is the correct model one (fairly straightforward) way of mapping this would be to introduce a class like this.
public class UserResource {
public virtual int UserResourceId { get; protected set; }
public virtual User User { get; set; }
public virtual Resource { get; set; }
public virtual string AccessLevel { get; set; }
}
and map it in this way:
public class UserResourceMap : ClassMap<UserResource> {
public UserResourceMap() {
Table("UserResourceMap");
Id(x => x.UserResourceId);
References(x => x.User).UniqueKey("UniqueUserAndResource");
References(x => x.Resource).UniqueKey("UniqueUserAndResource");
Map(x => x.AccessLevel);
}
}
If you want bidirectional associations you could also add a Collection property on User and/or Resource and map these with HasMany(...).Inverse(). Of course, this kind of mapping would introduce a new UserResourceId column in the UserResourceMap table (using a composite key consisting of User and Resource would mitigate that).
Another solution would be to add an EntityMap association. If the association is owned by User it would be a Dictionary<Resource, string> property. Something like this might do the trick:
public class User {
public virtual int UserId { get; protected set; }
public virtual string Name { get; set; }
public virtual Dictionary<Resource, string> Resources { get; set; } // Resource -> AccessLevel
}
public class UserMap : ClassMap<User> {
public UserMap() {
Table("User");
Id(x => x.UserId);
Map(x => x.Name);
HasMany<Resource, string>(x => x.Resources).AsEntityMap().Element("AccessLevel");
}
}
As you've correctly identified in your database schema, this isn't a pure many-to-many relationship - it's two one-to-many relationships as the intermediate table has an attribute (the access level).
I therefore think your domain is missing an entity - there doesn't appear to be any relationship in your model between a user and the resources they can access.
How about something like this:
public class User
{
public virtual int Id { get;protected set; }
public virtual string Name { get;set; }
public virtual ICollection<UserResource> UserResources { get; set;}
}
public class UserResource
{
public virtual int Id { get; protected set; }
public virtual User User { get; set;}
public virtual Resource Resource { get; set;}
public virtual string AccessLevel { get; set;}
}
public class Resource
{
public virtual int Id { get;protected set; }
public virtual string Name { get;set; }
}
And mappings like:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.UserResource)
.AsSet()
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
public class UserResourceMap : ClassMap<UserResource>
{
public UserResourceMap()
{
Table("UserResourceMap");
Id(x => x.Id);
References(x => x.User).Not.Nullable();
References(x => x.Resource).Not.Nullable();
Map(x => x.AccessLevel);
}
}
public class ResourceMap : ClassMap<Resource>
{
public ResourceMap()
{
Cache.ReadOnly();
Id(x => x.Id);
Map(x => x.Name);
}
}
I am using IQueryable<> to build up batching queries.
I have used views successfully to fetch information so the IQueryable<> can find it, but in this instance I can't work out how to map a view, as it depends on properties rather than the entity's ID.
Say I have this entity and mapping:
public class Calculation
{
public virtual int Id { get; set; }
public virtual Organisation Organisation { get; set; }
public virtual Charge Charge { get; set; }
public virtual TransactionTotal TransactionTotal { get; set; }
}
public class CalculationMap : ClassMap<Calculation>
{
public CalculationMap()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.Organisation).Not.Nullable().UniqueKey("OC");
References(x => x.Charge).Not.Nullable().UniqueKey("OC");
}
This is the class I need to get in there: I'm using a view to give me the total amount per Organisation and Charge:
public class TransactionTotal
{
public virtual int Id { get; set; }
public virtual Organisation Organisation { get; set; }
public virtual Charge Charge { get; set; }
public virtual decimal Amount { get; set; }
}
public class TransactionTotalMap : ClassMap<TransactionTotal>
{
public TransactionTotalMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Table("TransactionTotalsView");
References(x => x.Charge).Not.Nullable();
References(x => x.Organisation).Not.Nullable();
Map(x => x.Amount).Precision(15).Scale(2).Not.Nullable();
}
}
Other places I have used views I have successfully used mappings like HasOne(x => x.TransactionTotal); but in this instance I need to tell Nhibernate to use the Organisation and Charge properties as the key.
Is this even possible? If so, how do I map the TransactionTotal class to the Calculation class?
Edit: I have used CompositeId in TransactionTotalMap as suggested by #David:
CompositeId().KeyProperty(x => x.Organisation.Id).KeyProperty(x => x.Charge.Id);
I'm still stuck on what to put in the CalculationMap though.
use the CompositeId() method in your mapping
I have following classes in my domain model:
public class Player
{
public virtual string ID { get; set; }
public virtual string Name { get; set; }
public virtual List<Match> Matches { get; set; }
}
public class Match
{
public virtual int ID { get; set; }
public virtual Player Player1 { get; set; }
public virtual Player Player2 { get; set; }
}
As you can see a Player can play multiple matches, and every match has two players.
How can I map these classes correctly using Fluent mapping?
The players on the match mapping would be References (many-to-one) referencing different playerIds in the match table and the matches on player would be hasMany (one-to-many):
public sealed class PlayerMap : ClassMap<Player>
{
public PlayerMap()
{
Id(x => x.ID).GeneratedBy.Native();
Map(x => x.Name);
HasMany(x => x.Matches).Cascade.SaveUpdate();
}
}
public sealed class MatchMap : ClassMap<Match>
{
public MatchMap()
{
Id(x => x.ID).GeneratedBy.Native();
References(x => x.Player1, "player1_id").NotFound.Ignore().Cascade.None();
References(x => x.Player2, "player2_id").NotFound.Ignore().Cascade.None();
}
}
You need a many-to-many (this case being a many-to-2). A possible fluent mapping is
public PlayerMap()
{
[...]
HasManyToMany(x => x.Matches).Cascade.AllDeleteOrphan();
}
otherwise nhibernate wouldn't know which column to use (match.player1_id or match.player2_id)