I have the following entities
public abstract class Card
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Product Product { get; set; }
public virtual Sprint Sprint { get; set; }
}
public class Story:Card
{
public virtual double Points { get; set; }
public virtual int Priority { get; set; }
}
public class Product
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Story> Stories { get; private set; }
public Product()
{
Stories = new List<Story>();
}
}
And the following mappings
public class CardMap:ClassMap<Card>
{
public CardMap()
{
Id(c => c.Id)
.Index("Card_Id");
Map(c => c.Name)
.Length(50)
.Not.Nullable();
Map(c => c.Description)
.Length(1024)
.Not.Nullable();
References(c=>c.Product)
.Not.Nullable();
References(c=>c.Sprint)
.Nullable();
}
}
public class StoryMap : SubclassMap<Story>
{
public StoryMap()
{
Map(s => s.Points);
Map(s => s.Priority);
}
}
public class ProductMap:ClassMap<Product>
{
public ProductMap()
{
Id(p => p.Id)
.Index("Product_Id");
Map(p => p.Name)
.Length(50)
.Not.Nullable();
HasMany(p => p.Stories)
.Inverse();
}
When I generate my Schema, the tables are created as follows
Card
---------
Id
Name
Description
Product_id
Sprint_id
Story
------------
Card_id
Points
Priority
Product_id
Sprint_id
What I would have expected would have been to see the columns Product_id and Sprint_id ONLY in the Card table, not the Story table.
What am I doing wrong or misunderstanding?
NB: Tested on the NH2 project only
Well, you are probably going to want to chew on a door once you read this, but the TLDR reason is because the Product_id and Spring_id columns in your Story table are not redundant - they exist for the HasMany(x => x.Stories) relations in your SpringMap and ProductMap. They just happen to be share the same naming convention as the CardMap References(x => x.Product and References(x => x.Sprint).
Validate this for yourself by commenting out ProductMap.cs:24-25 and SprintMap.cs:22 and rebuilding.
If the above does not make sense, let me know and I will try to explain in further detail.
So, it should work fine as is. If you want to clarify the columns, you could explicitly define the column names like so:
ProductMap.cs
HasMany(p => p.Stories)
.KeyColumn("ProductOwner_id")
.Inverse();
SprintMap.cs
HasMany(s => s.Stories)
.KeyColumn("SprintOwner_id")
;
CardMap.cs
References(c=>c.Product)
.Column("Product_id")
.Not.Nullable();
References(c=>c.Sprint)
.Column("Sprint_id")
.Nullable();
Here I am guessing that the 1:N relationships between a Story and a Product/Sprint are an "owner". You would want to rename it to whatever is appropriate semantically.
One other thing. I would have thought the last changes (the changes to CardMap.cs) would be unnecessary - but they seem to be for some reason, or the Sprint_id column becomes SprintOwner_id. I have no idea why this would happen - I would speculate that this is some sort of bidirectional relationship inferencing on fluent/nhibernates part gone awry, but I'd put very little money on that.
I see that the Story entity inherits from the Card entity you created, but you don't know why you have Product_Id and Sprint_Id properties in the Story table Schema, since they're virtual properties in the Card class.
I'm guessing that this happens because in NHibernate, all properties need to be virtual but only at first. They don't really stay virtual. The NHibernate framework overrides them, and probably because of this, this is happening to you.
Related
I have two classes:
public class Student
{
public long StudentId { get; set; }
public StudentDetails details { get; set; }
}
public class StudentDetails
{
public long StudentDetailsId { get; set; }
public Student student{ get; set; }
//other properties
}
One student contains one studentdetails. A studentdetails can not exist without a corresponding student.
With these mappings:
public StudentMapping()
{
this.ToTable("Student");
this.HasKey(x => x.StudentId);
this.HasRequired(x => x.details)
.WithRequiredDependent(x => x.student)
.WillCascadeOnDelete(true);
}
public StudentDetailsMapping()
{
this.ToTable("StudentDetails");
this.HasKey(x => x.StudentDetailsId);
this.HasRequired(x => x.student);
}
However, when I go to the database in SQL Management Studio, and do the following: DELETE FROM STUDENTS WHERE StudentId == 1, the Student row gets deleted, but the delete does not cascade to the studentdetails row. What's going wrong? I am trying to get the StudentDetails row to get deleted when I delete it's Student parent object.
Have you checked this SO and this article on MSDN?
As I can see, your model is not a real one-on-one relationship because the two entities you have here doesn't share the same primary key.
When you model a relationship like that you are, in fact, creating a one-to-many table structure in database: how can you prevent that StudentDetailsId will not be in use for another student? I mean, you can enforce it with a business rule, but strictly db speaking there no rule.
If you want to enforce cascade delete with EF in a one-to-one, you need to make something like this:
public class Student
{
public long StudentId { get; set; }
public StudentDetails details { get; set; }
}
public class StudentDetails
{
public long StudentId { get; set; }
public Student student{ get; set; }
//other properties
}
public StudentMapping()
{
this.ToTable("Student");
this.HasKey(x => x.StudentId);
this.HasRequired(x => x.details)
.WithRequiredDependent(x => x.student)
.WillCascadeOnDelete(true);
}
public StudentDetailsMapping()
{
this.ToTable("StudentDetails");
this.HasKey(x => x.StudentId);
this.HasRequired(x => x.student);
}
Hope it helps :)
I have 2 entities with a many to many relationship:
[Table("Student", Schema = "School")]
public class Student
{
public long ID { get; set; }
public virtual List<Teacher> Teachers { get; set; }
//...
}
[Table("Teacher", Schema = "School")]
public class Teacher
{
public long ID { get; set; }
public virtual List<Student> Students { get; set; }
//...
}
I've specified in the fluent API how to construct the join table as such:
public StudentMap(string schema)
{
//Where schema = "School"
HasMany(p => p.Teachers)
.WithMany(p => p.Students)
.Map(m =>
{
m.ToTable("StudentsTeachers", schema);
m.MapLeftKey("Student_ID");
m.MapRightKey("Teacher_ID");
});
}
However, when I go to access the Teachers navigation object on the Student, it defaults to the EF convention as opposed to what I've designated for the join table. How would I go about specifying to the SchoolContext that we should be looking at School.StudentsTeachers table instead of dbo.StudentTeachers ?
The problem isn't in the designation of the join table, or the many to many relationship being generated. Those worked fine. The problem arises when attempting to use the Entities, I need a way to specify the relationship should use the Join table I specified, as opposed to the EF naming convention. I was able to resolve a similar issue with EF using its conventions over my table names by using the Table Attribute as shown above. I'm now looking for an equivalent answer except with regards to the many to many join table that exists, but doesn't have an explicit model
I think this should work:
[Table("Student", Schema = "School")]
public class Student
{
[Key,Column("Student_ID")]
public long ID { get; set; }
public virtual List<Teacher> Teachers { get; set; }
//...
}
[Table("Teacher", Schema = "School")]
public class Teacher
{
[Key,Column("Teacher_ID")]
public long ID { get; set; }
public virtual List<Student> Students { get; set; }
//...
}
public StudentMap()
{
HasMany(p => p.Teachers)
.WithMany(p => p.Students)
.Map(m =>
{
m.ToTable("StudentsTeachers", "School");
m.MapLeftKey(p => p.ID);
m.MapRightKey(p => p.ID);
});
}
I'm using a data structure similar to this where type of animal is determined from a discriminator column in the table:
public class Farm {
public int Id { get; set; }
public virtual ICollection<Pig> Pigs { get; set; }
public virtual ICollection<Cow> Cows { get; set; }
}
public class Animal {
public int Id { get; set; }
public int FarmId? { get; set; }
public virtual Farm Farm { get; set; }
public string Name { get; set; }
}
public class Pig : Animal {}
public class Cow : Animal {}
Mapping:
this.Map<Pig>(m => m.Requires("Type").HasValue((int) AnimalType.Pig));
this.Map<Cow>(m => m.Requires("Type").HasValue((int) AnimalType.Cow));
But I can't seem to map the relationship between the Pigs, Cows and Farm. I've tried this from FarmMap which gives a duplicate column mapping error:
this.HasMany(t => t.Pigs)
.WithOptional(t => t.Farm)
.Map(m => m.MapKey("FarmId"));
this.HasMany(t => t.Cows)
.WithOptional(t => t.Farm)
.Map(m => m.MapKey("FarmId"));
Mapping from each of the animals doesn't work either, it generates extra columns (eg. Farm_Id and Farm_Id1 - in addition to FarmId - one for each animal type).
this.HasOptional(t => t.Farm)
.WithMany(t => t.Pigs)
.HasForeignKey(d => d.FarmId)
Moving the navigation property from the Animal model to the inheriting models causes a single additional column to be generated - FarmId1 (so a little closer to what I want than the above 2!)
Is there any way to achieve this?
I'm no EF expert but from the Model-first approach I know that this would be mapped as a collection of Animal, you can then select Farm.Animals.OfType<Pig>()
I think I have a design issue here.
essentially I have a class called office
class Office
{
public virtual long Id { get; set; }
public virtual string Code { get; set; }
public virtual IList<Person> Managers { get; set; }
public virtual IList<Person> Developers { get; set; }
public virtual IList<Person> TeaMakers { get; set; }
}
and a class called Person
class Person
{
public virtual long Id { get; set; }
public virtual string Name {get; set;}
public virtual StaffType Type { get; set;}
public virtual Office Office { get; set; }
}
and an enum called StaffType
public enum StaffType
{
MANAGER,
DEVELOPER,
TEAMAKER
}
Mapping the Person table is easy:
public class PersonMap: ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.Id);
Map(x => x.Name);
References(x => x.Office).ForeignKey("Id").Not.Nullable()
Map(x => x.Type).CustomType<StaffType>();
}
}
but i am stumped on the office map. how to i get the map to use the enum to filter the 3 lists?
if i do this:
public class OfficeMap: ClassMap<Office>
{
public static string TableName = "Office";
public static string MappingColumn = TableName + "Id";
public OfficeMap()
{
Table(TableName);
Id(x => x.Id);
Map(x = x.Code);
HasMany(x => x.Managers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
HasMany(x => x.Developers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
HasMany(x => x.TeaMakers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
}
}
fluency won't have the foggiest idea how to split the 3 collections up by the StaffType enum
Thanks for the help
Extra note: the Person table's Type field allways gets mapped as an int.
NHibernate supports filtering as a part of the mapping. Please, read here more 6.2. Mapping a Collection.
The trick is to add more SQL into the mapping. In fact, some WHERE Condition, to be evaluated during the collection load. Small extract from the documentation:
<map // or <set or <bag ...
name="propertyName" (1)
table="table_name" (2)
...
where="arbitrary sql where condition" (9)
And the description of the WHERE:
where (optional) specify an arbitrary SQL WHERE condition to be used
when retrieving or removing the collection (useful if the collection
should contain only a subset of the available data)
In your case, the fluent syntax is similar: ...Where("MyColumn = 'myValue' ");
A draft for your solution:
...
HasMany(x => x.Managers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn)
.Where("Type = 1") // the Column name in the Person table
; // and the value 1 as the enum of the Manager
...
// The same for the others
I would model this as a simple one-to-many (Office has many Person) and add an extension method to IEnumerable<Person> to filter by StaffType. If needed, you can encapsulate access to the Person collection through AddManager etc. methods that enforce business rules.
I am using Nhibernate 3.2, along with a build of FluentNhibernate compatible with NH 3.2 and I have come to map a legacy part of my system. I believe it is possible to do what I require, but need some assistance in mapping it correctly.
I have a User Class below, with a list of Applications.
public class User
{
public virtual string UserID { get; set; }
public virtual int Id { get; set; }
public virtual IList<Application> Applications { get; set; }
}
I also have a Application class which has a foreign key back to the User Class which is not the "primary key". See Below
public class Application
{
// Not sure if this 'User' is needed?
public virtual User User { get; set; }
public virtual int IdFromUserTable { get; set; }
public virtual string ApplicationName { get; set; }
}
I have the corresponding ClassMaps, I think the UserMap is where the issue lies
UserMap
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.UserID);
HasMany(x => x.Applications)
.AsBag()
.KeyColumn("UserID")
.PropertyRef("Id")
.LazyLoad()
.Inverse();
Table("usertable");
ReadOnly();
}
}
ApplicationMap
public class ApplicationMap : ClassMap<Application>
{
public ApplicationMap()
{
CompositeId()
.KeyProperty(x => x.ApplicationName)
.KeyProperty(x => x.IdFromUserTable, "UserID");
Table("applicationstable");
}
}
The Table structures are as follows:
User Table
Primary Key - string, ColumnName = UserID
int (Identity) - int, ColumnName = Id
Applications Table
Composite Key - string, ColumnName = ApplicationName
and
int, ColumnName = UserId (references Id column in user table)
My question is how to get the mapping to work for the above scenario.
One caveat is: I cannot change the structure of the database, but I can change the classes / classmaps
Any help greatly appreciated..
PS - I did get this to work by adding in Fetch.Join in the HasMany userMap, but I would prefer to lazily evaluate the Application List when needed
Thanks, Mark