I have a table http://img36.imageshack.us/i/beztytuuszxq.png/ and mapping:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table(FieldNames.Category.Table);
Id(x => x.ID);
Map(x => x.Name).Not.Nullable();
Map(x => x.ShowInMenuBar).Not.Nullable();
References(x => x.Parent).Column(FieldNames.Category.ID).Nullable();
HasMany(x => x.Articles).Cascade.All().Inverse().Table(FieldNames.Article.Table);
}
}
The entity looks that:
public class Category : EntityBase
{
public virtual int ID { set; get; }
public virtual string Name { set; get; }
public virtual Category Parent { set; get; }
public virtual bool ShowInMenuBar { set; get; }
public virtual IList<Article> Articles { set; get; }
}
When I want to save an Category object to db, when Parent property is set to null, I have exception:
not-null property references a null or transient value CMS.Domain.Entities.Article.Category
I cannot change the
public virtual Category Parent { set; get; }
line to
public virtual Category? Parent { set; get; }
or
public virtual Nullable<Category> Parent { set; get; }
because I have an error during compile:
CMS.Domain.Entities.Category' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'
I don't know what to change to have possibility to save Category objects without parents.
You can't make a reference type Nullable (as it already is). Nullable<T> (or T?) can only be used with a non-nullable value type (such as int or DateTime).
The error refers to CMS.Domain.Entities.Article.Category - the Category property in the Article class. You haven't provided the map file for the Article entity, however I assume it maps the Category property and either specifies Not.Nullable() or doesn't specific Nullable().
If the domain model allows for the Article entity to contain a null Category use Nullable() otherwise you need to set the category when creating/saving the Article:
Article.Category = aCategory;
The reason you can't Nullable the Category is because Nullable is only meant for value types, and Category, by definition, is a reference type and therefore already can support nulls on properties defined as Category. Can you provide a full stack trace of the exception?
Im guessing you are trying to save an article, (you specified inverse) therefore you need this:
Article.Category = category;
Related
I have a class of Content which should be able to have a parentId for inheritance but also I want it to have a list of child content which is nothing to do with this inheritance tree.
I basically wanted a link table as ChildContentRelationship with Id's for parentContent and childContent in it and the Content class would have a list of ChildContentRelationship.
This has caused a lot of errors.
Here's waht I sort of want to do
public class Content
{
public int Id { get; set; }
public int? ParentContentId { get; set; }
public virtual Content ParentContent { get; set; }
public string Name { get; set; }
public int ContentTypeId { get; set; }
public virtual ContentType ContentType { get; set; }
public virtual ICollection<Property> Properties { get; set; }
public virtual ICollection<ChildContentRelationship> ChildContent { get; set; }
}
How would I set this up in EF?
I am not sure if I understand your model correctly. Let's discuss the options.
For a moment I omit this additional entity ChildContentRelationship and I assume the ChildContent collection is of type ICollection<Content>.
Option 1:
I assume that ParentContent is the inverse property of ChildContent. It would mean that if you have a Content with Id = x and this Content has a ChildContent with Id = y then the ChildContents ParentContentId must always be x. This would only be a single association and ParentContent and ChildContent are the endpoints of this same association.
The mapping for this relationship can be created either with data annotations ...
[InverseProperty("ParentContent")]
public virtual ICollection<Content> ChildContent { get; set; }
... or with Fluent API:
modelBuilder.Entity<Content>()
.HasOptional(c => c.ParentContent)
.WithMany(c => c.ChildContent)
.HasForeignKey(c => c.ParentContentId);
I think this is not what you want ("...has nothing to do with..."). Consider renaming your navigation properties though. If someone reads Parent... and Child... he will very likely assume they build a pair of navigation properties for the same relationship.
Option 2:
ParentContent is not the inverse property of ChildContent which would mean that you actually have two independent relationships and the second endpoint of both relationships is not exposed in your model class.
The mapping for ParentContent would look like this:
modelBuilder.Entity<Content>()
.HasOptional(c => c.ParentContent)
.WithMany()
.HasForeignKey(c => c.ParentContentId);
WithMany() without parameters indicates that the second endpoint is not a property in your model class, especially it is not ChildContent.
Now, the question remains: What kind of relationship does ChildContent belong to? Is it a one-to-many or is it a many-to-many relationship?
Option 2a
If a Content refers to other ChildContents and there can't be a second Content which would refer to the same ChildContents (the children of a Content are unique, so to speak) then you have a one-to-many relationship. (This is similar to a relationship between an order and order items: An order item can only belong to one specific order.)
The mapping for ChildContent would look like this:
modelBuilder.Entity<Content>()
.HasMany(c => c.ChildContent)
.WithOptional(); // or WithRequired()
You will have an additional foreign key column in the Content table in your database which belongs to this association but doesn't have a corresponding FK property in the entity class.
Option 2b
If many Contents can refer to the same ChildContents then you have a many-to-many relationship. (This is similar to a relationship between a user and roles: There can be many users within the same role and a user can have many roles.)
The mapping for ChildContent would look like this:
modelBuilder.Entity<Content>()
.HasMany(c => c.ChildContent)
.WithMany()
.Map(x =>
{
x.MapLeftKey("ParentId");
x.MapRightKey("ChildId");
x.ToTable("ChildContentRelationships");
});
This mapping will create a join table ChildContentRelationships in the database but you don't need a corresponding entity for this table.
Option 2c
Only in the case that the many-to-many relationship has more properties in addition to the two keys (ParentId and ChildId) (for example something like CreationDate or RelationshipType or...) you would have to introduce a new entity ChildContentRelationship into your model:
public class ChildContentRelationship
{
[Key, Column(Order = 0)]
public int ParentId { get; set; }
[Key, Column(Order = 1)]
public int ChildId { get; set; }
public Content Parent { get; set; }
public Content Child { get; set; }
public DateTime CreationDate { get; set; }
public string RelationshipType { get; set; }
}
Now your Content class would have a collection of ChildContentRelationships:
public virtual ICollection<ChildContentRelationship> ChildContent
{ get; set; }
And you have two one-to-many relationships:
modelBuilder.Entity<ChildContentRelationship>()
.HasRequired(ccr => ccr.Parent)
.WithMany(c => c.ChildContent)
.HasForeignKey(ccr => ccr.ParentId);
modelBuilder.Entity<ChildContentRelationship>()
.HasRequired(ccr => ccr.Child)
.WithMany()
.HasForeignKey(ccr => ccr.ChildId);
I believe that you want either option 2a or 2b, but I am not sure.
I am puzzled and frustrated by an exception I'm getting via NHibernate. I apologize for the length of this post, but I've tried to include an appropriate level of detail to explain the issue well enough to get some help!
Here's the facts:
I have a Person class which contains a property BillingManager, which is also a Person type. I map this as an FNH "Reference".
I have an ExpenseReport class which contains a property SubmittedBy, which is a Person type. I map this as an FNH "Reference".
I have a BillableTime class which contains a property Person, which is a Person type. I map this as an FNH "Reference".
Person contains a collection (IList) of ExpenseReport types (property ExpenseReports)
Person contains a collection (IList) of BilledTime types (property Time)
(See classes and mappings at bottom of post.)
All was cool until I added the IList<BilledTime> Time collection to Person. Now, when I try to access _person.Time, I get an exception:
The code:
// Get billable hours
if (_person.Time == null ||
_person.Time.Count(x => x.Project.ProjectId == project.ProjectId) == 0)
{
// No billable time for this project
billableHours = Enumerable.Repeat(0F, 14).ToArray();
}
The exception:
could not initialize a collection:
[MyApp.Business.Person.Time#211d3567-6e20-4220-a15c-74f8784fe47a]
[SQL: SELECT
time0_.BillingManager_id as BillingM8_1_,
time0_.Id as Id1_,
time0_.Id as Id1_0_,
time0_.ReadOnly as ReadOnly1_0_,
time0_.DailyHours as DailyHours1_0_,
time0_.Week_id as Week4_1_0_,
time0_.Person_id as Person5_1_0_,
time0_.Project_id as Project6_1_0_,
time0_.Invoice_id as Invoice7_1_0_
FROM [BillableTime] time0_
WHERE time0_.BillingManager_id=?]
It's true that BillingManager_id is an invalid column name, it doesn't exist in the BillableTime table. However, I don't understand why NHB has created this SQL... doesn't make sense to me. I have seen this "Invalid column name" exception a lot when searching for a solution, but nothing seems to work. Even more confusing: like BilledTime, the ExpenseReport type also contains a reference to Person and it works perfectly.
One thing I was able to figure out is that if I remove the BillingManager reference from the Person mapping (References(p => p.BillingManager)), the exception goes away and things seem to work (with respect to BillableTime; it of course breaks the BillingManager persistence). Now it seems like there is some "self-reference" problem, since the Person.BillingManager property is itself a reference to a Person.
Any idea what is going on here? I'm at a loss...
Thanks.
=== Classes & Mappings ===
public class Person
{
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual Person BillingManager { get; set; }
public virtual IList<ExpenseReport> ExpenseReports { get; set; }
public virtual IList<BillableTime> Time { get; set; }
}
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(p => p.UserId).GeneratedBy.Assigned();
Map(p => p.LastName).Not.Nullable();
Map(p => p.FirstName).Not.Nullable();
References(p => p.BillingManager);
HasMany(p => p.ExpenseReports).Cascade.AllDeleteOrphan();
HasMany(p => p.Time).Cascade.AllDeleteOrphan();
}
}
public class BillableTime
{
public virtual int Id { get; private set; }
public virtual Week Week { get; set; }
public virtual Person Person { get; set; }
public virtual Project Project { get; set; }
public virtual float[] DailyHours { get; set; }
public virtual Invoice Invoice { get; set; }
public virtual bool ReadOnly { get; set; }
}
public class BillableTimeMapping : ClassMap<BillableTime>
{
public BillableTimeMapping()
{
Id(x => x.Id);
References(x => x.Week);
References(x => x.Person);
References(x => x.Project);
References(x => x.Invoice);
Map(x => x.ReadOnly).Not.Nullable().Default("0");
Map(x => x.DailyHours).Length(28);
}
}
public class ExpenseReport
{
public virtual long Id { get; set; }
public virtual Person SubmittedBy { get; set; }
}
the following line should solve the issue, but i' dont know exactly why it is happening. if i have the spare time i will investigate.
HasMany(p => p.Time).Cascade.AllDeleteOrphan().KeyColumn("Person_Id");
I need to persist this class on database using Fluent NHibernate:
public class RaccoonCity
{
public virtual int Id { get; private set; }
public virtual DateTime InfectionStart { get; private set; }
private IList<Zombie> _zombies = new List<Zombie>();
public virtual IEnumerable<Zombie> Zombies
{
get { return _zombies; }
}
protected RaccoonCity()
{}
public RaccoonCity(DateTime startMonth)
{
InfectionStart = startMonth;
}
public virtual void AddZombie(Zombie z)
{
_zombies.Add(z);
}
}
The property has type IEnumerable to indicate that you shouldn´t use it to insert new items. The backing field is of IList to make it easy to insert new items from the own class.
Zombie is a simple class:
public class Zombie
{
public virtual int Id { get; private set; }
public virtual string FormerName { get; set; }
public virtual DateTime Infected { get; set; }
}
The map is the following:
public class RaccoonCityMap: ClassMap<RaccoonCity>
{
public RaccoonCityMap()
{
Id(x => x.Id);
Map(x => x.InfectionStart);
HasMany(x => x.Zombies)
.Access.CamelCaseField(Prefix.Underscore)
.Inverse()
.Cascade.All();
}
}
When I test this, the data is inserted in database, but the zombie´s foreign keys are empty, and the RaccoonCity instance has zero items on Zombies list.
You are declaring the relationship as Inverse, which means the Zombie and not the RacoonCity is responsible for maintaining the relationship.
Either add the corresponding reference to zombie and set it on the AddZombie method, or remove the Inverse (in that case, you'll see an INSERT with a null FK followed by an update).
Suggested reading: http://nhibernate.info/doc/nh/en/index.html#collections-onetomany
Found a post about it: https://web.archive.org/web/20090831052429/http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/08/15/a-fluent-interface-to-nhibernate-part-3-mapping.aspx
I had to implement the method
HasManyComponent by myself since it
was missing in the actual trunk of the
framework. That is, it was not
possible to map a collection of value
objects. But it has not been that hard
since the source base is really nice.
My changes will probably be integrated
into the framework soon.
And this one:
http://nhforge.org/blogs/nhibernate/archive/2008/09/06/a-fluent-interface-to-nhibernate-part-3-mapping-relations.aspx
What is the best way of mapping a simple Dictionary property using Fluent NHibernate?
public class PersistedData
{
public virtual IDictionary<key, value> Dictionary { get; set; }
}
public class PersistedDataMap : ClassMap<PersistedData>
{
HasMany(x => x.Dictionary)
.Table("dict_table")
.KeyColumn("column_id")
.AsMap<string>("key")
.Element("value");
}
This will properly map Dictionary to table dict_table and use column_id to associate it to the base id.
As a side note, if you would like to use an Enum as the Key in the dictionary, it should be noted that NHibernate.Type.EnumStringType<MyEnum> can be used in place of the string in .AsMap<string> to use the string value instead of the Ordinal.
Using a simple class relationship such as the following:
public class Foo {
public virtual IDictionary<string, Bar> Bars { get; set; }
}
public class Bar {
public virtual string Type { get; set; }
public virtual int Value { get; set; }
}
You can map this with Fluent NHibernate in this way:
mapping.HasMany(x => x.Bars)
.AsMap(x => x.Type);
Where Bar.Type is used as the key field into the dictionary.
To map a list as a dictionary:
HasMany(x => x.Customers)
.AsMap();
I have not used it; so cannot give an example.
Have look at the wiki: Cached version of the page, Actual page I have given the cached version of the page as the site seems to be down.
Using Fluent NHibernate I need a clue how to map my Invoice class.
public class Buyer
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string TaxRegNo { get; set; }
// .... more properties....
}
public class Invoice
{
public virtual int Id { get; set; }
public virtual int IdBuyer { get; set; }
public virtual Buyer Buyer { get; set; }
// ....more properties
}
The problem is that I want to have in Invoice class:
BuyerId - just an integer ID for reference and foregin key relationship
a copy of almost all buyer properties (its accounting document and properties cannot be changed after confirmation) - as component
I tried to this using following mapping but it doesn't work
public InvoiceMap()
{
Id(x => x.Id);
References(x => x.IdBuyer);
Component(x => x.Buyer, BuyerMap.WithColumnPrefix("buyer_"));
// ....more properties
}
You would normally not map both the foreign key and the child object. If you do map both, then do this in the mapping (or similar):
References(x => x.Buyer);
Map(x => x.IdBuyer).Column("BuyerId").Not.Insert().Not.Update();
Then you don't double up on the column name in SQL statements, which causes errors around mismatched numbers of parameters.