I want to control my domain's interaction with a collection, so I thought I'd make the collection protected and provide a read-only wrapper around it so that the contents are visible, but I can ensure that items are not added directly to the collection.
So I have the following code:
public class MyClass
{
public virtual ICollection<Thread> Threads
{
get { return new ReadOnlyWrappedCollection<Thread>(this.ThreadsInternal); }
}
protected virtual ICollection<Thread> ThreadsInternal { get; private set; }
}
I tried this:
this.Map(c => c.Threads)
.Access.None();
The result was a MappingException: Could not determine type for: System.Collections.Generic.ICollection'1[[Thread]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, for columns: NHibernate.Mapping.Column(Threads)
I tried this:
this.HasMany(c => c.Threads)
.Access.None();
The result was an InvalidOperationException: Tried to add collection 'Threads' when already added
If I omit the mapping, I get PropertyNotFoundException: Could not find a setter for property 'Threads' in class 'MyClass'
How can I persuade NHibernate to ignore this property in the mapping? I'm using Fluent NHibernate, but please post examples in hbm too.
I don't think you can map an ICollection. Regardless, I'm following a similar pattern and I've found that the best way to map it is to map a private IList.
Class:
public class Invoice
{
private IList<InvoiceItem> _items;
public Invoice()
{
_items = new List<InvoiceItem>();
}
public virtual IEnumerable<InvoiceItem> Items
{
get { return _items; }
}
}
Mapping:
public class InvoiceMap : ClassMap<Invoice>
{
public InvoiceMap()
{
Table("Invoice");
HasMany(x => x.Items).KeyColumn("InvoiceId")
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.AllDeleteOrphan()
.Inverse()
.AsBag().LazyLoad();
}
}
The key line in the mapping is .Access.CamelCaseField(Prefix.Underscore) which tells NHibernate to use the private field _items. Note that The collection can still be cast to IList, but you could wrap it in a read only collection if needed.
Related
I'm trying to map (by code) a protected collection to a bag but I'm struggling. e.g.
public class MyClass
{
....
protected virtual ICollection<Items> MyItems { get; set; }
....
}
public class MyClassMapping : ClassMapping<MyClass>
{
...
Bag(x => x.MyItems, map =>
{
....
}
...
}
It throws a mapping exception with the inner exception being "ArgumentNullException: Value cannot be null. Parameter name: localMember". It works fine if the "MyItems" collection is public.
I followed this article (https://groups.google.com/forum/#!topic/nhusers/wiH1DPGOhgU) which recommends using the method overload that takes a string. e.g.
public class MyClassMapping : ClassMapping<MyClass>
{
...
Bag("MyItems", map =>
{
....
}
...
}
But this gives a compilation error "The type arguments for method .... cannot be inferred from the usage. Try specifying the type arguments explicitly".
Is it possible to map to a protected collection (I'm using NH 3.3)? Can someone give me an example?
Thanks,
Chet
As we can see the overloaded method here: PropertyContainerCustomizer.cs
public void Bag<TElement>(string notVisiblePropertyOrFieldName
, Action<IBagPropertiesMapper<TEntity, TElement>> collectionMapping
, Action<ICollectionElementRelation<TElement>> mapping)
{ ... }
What we have to pass as the generic template is the TElement, the one used as ICollection<TElement>.
And because the defintion is:
// TElement is Items
protected virtual ICollection<Items> MyItems { get; set; }
SOLUTION: What we have to do is declare the mapping like this
// the TElement must be expressed explicitly as Items
Bag<Items>("MyItems", map =>
{
....
}
Given the classes below:
public class Address : Place
{
public virtual string Street { get; set; }
public virtual int Number { get; set; }
public override string WhereAmI
{
get { string.Format("{0} {1}", Street , Number); }
}
}
public abstract class Place : DomainEntity
{
public abstract string WhereAmI { get; }
}
When I use this mapping:
var autoMap = AutoMap.AssemblyOf<Party>()
.Override<Place>(map => map.IgnoreProperty(p => p.WhereAmI))
.Override<Address>(map => map.IgnoreProperty(p => p.WhereAmI))
.Where(type => type.Namespace != null && type.Namespace.Contains("Models"));
I still get the error: Could not find a setter for property 'WhereAmI' in class 'Address'
Things I did:
When i remove the property from the base class "Address" it works.
When i use .OverrideAll(map => map.IgnoreProperty("WhereAmI")) But I don't want it to be global because in another class i might use the same property name where I DO want to include this Property
Is there any way to get this to work other then to use an Interface?
I tried tracking down in the FluentNHibernate code exactly why the IgnoreProperty seems to break down when the property being ignored is coming from a base class, but ran out of time. It seems to work fine if the get-only property is not coming from a base class.
Anyway, the solution to your situation seems to be to create a custom IAutomappingConfiguration by inheriting from DefaultAutomappingConfiguration. See this stack overflow answer: How can I create a Fluent NHibernate Convention that ignores properties that don't have setters.
Here's the custom automapping configuration that I used successfully to automap the example entity you provided:
protected class CustomConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap (Member member)
{
if (member.IsProperty && member.IsPublic && !member.CanWrite)
{
return false;
}
return base.ShouldMap(member);
}
public override bool ShouldMap(Type type)
{
return type.Namespace != null && type.Namespace.Contains("Models");
}
}
And then its use:
var autoMap = AutoMap
.AssemblyOf<DomainEntity>(new CustomConfiguration());
Note that the Where clause in your example had to move into the custom configuration class as its not allowed to be chained if you are using a custom configuration instance.
I receive the following exception intermittently:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: Domain.Foo.Bars
The majority of the results on Google for this exception indicate that the problem occurs when you dereference a collection, instead of calling Clear() on the existing collection then adding new entities. However, this is not my problem.
Here is all the relevant code:
public class Foo
{
public int Id { get; set; }
private Iesi.Collections.Generic.ISet<Bar> _bars = new HashedSet<Bar>();
public virtual ICollection<Bar> Bars
{
get { return _bars; }
}
}
public class Bar
{
public int Id { get; set; }
public DateTime Expiry { get; set; }
}
public class FooDbMap : ClassMap<Foo>
{
public FooDbMap
{
Id(x => x.Id);
HasMany(x => x.Bars)
.Access.CamelCaseField(Prefix.Underscore)
.KeyColumn("FooId")
.LazyLoad()
.Where("Expiry > (select getdate())")
.AsSet()
.Cascade.AllDeleteOrphan();
}
}
You will see with this code that it is impossible to dereference the Bars collection, i.e. by doing this:
foo.Bars = new List<Boo>();
What could be causing the error?
you should never mess with collection references created by NHibernate. First of all, NH creates proxy objects for lazy loading - replace that proxy with a List<> and NH has no way to either lazy load the contents OR detect wether any children have been removed. Secondly, NH watches mapped collections for changes (new entities, deletes etc.). Just replacing a collection with a new one that NH does know nothing about is not a good idea. NH will still have a reference to the managed collection, especially if you have NH monitor this collection and cascade all changes to contained children.
I would simply clear the collection to remove all entities instead of replacing the entire collection.
Disclaimer: I'm fairly new to NH & ORM in general.
Disclaimer: I'm working with a build of FNH from here in order to use with NH3.0GA.
The problem in a nutshell is that I would like to use FNH's SubclassMap as a way to map a LEFT JOIN, table-per-subclass scenario to my object hierarchy which is defined as:
public class MyBaseClass {
public virtual int Id { get; set; }
}
public class MySubClass : MyBaseClass {
public virtual string SubClassVal { get; set; }
}
This is mapped via FNH as:
public class MyBaseClassMap : ClassMap<MyBaseClass> {
public MyBaseClassMap() {
Table("BaseClass");
Id(x => x.Id, "Id").GeneratedBy.Assigned();
}
}
public class MySubClassMap : SubclassMap<MySubClass> {
public MySubClassMap() {
Table("SubClass");
KeyColumn("Id");
Map(x => x.SubClassVal);
}
}
And I retrieve via:
public class Repository {
ISession session; //assume properly initialized ISession
public IList<T> GetAll<T>() where T: class {
return session.CreateCriteria<T>().List<T>();
}
}
And in my database, I've got 1 record in my BaseClass table, 0 records in SubClass.
Now, what I would like to do is pull the entity out as a MySubClass instance by doing something like this:
var rep = new Repository();
var subclasses = rep.GetAll<MySubClass>();
And of course, there are no instances in the returned collection as this is presumably performing an INNER JOIN underneath it all. This is where I'm stuck. I've managed to discover that specifying an 'optional' join is what I'm supposed to do. I've attempted to modify MySubClassMap to:
public class MySubClassMap : SubclassMap<MySubClass> {
public MySubClassMap() {
Join("SubClass", j => {
j.KeyColumn("Id");
j.Optional();
j.Map(x => x.SubClassVal); // note that I've tried the map outside the Join() below, to no avail
});
//Map(x => x.SubClassVal);
}
}
Compiling/running this presents me with the following (innermost) exception:
The element 'joined-subclass' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'join' in namespace 'urn:nhibernate-mapping-2.2'. List of possible elements expected: 'property, many-to-one, one-to-one, component, dynamic-component, properties, any, map, set, list, bag, idbag, array, primitive-array, joined-subclass, loader, sql-insert, sql-update, sql-delete, resultset, query, sql-query' in namespace 'urn:nhibernate-mapping-2.2'.
I'll save posting the stack trace, but the jist of it is:
MyApp -->
FluentNHibernate.Cfg.FluentConfiguration.BuildSessionFactory() -->
NHibernate.Cfg.FluentConfiguration.BuildConfiguration()
I think that's all the relevant info. I suspect I may be bumping into a breaking change between this very new version of NH and version of FNH that isn't so new. But, as mentioned earlier, I am a rookie, and could well be doing something stupid. If this is the case, I'd very much appreciate somebody smacking me over the head with what probably should be obvious.
Thanks in advance.
Entities have one type, which doesn't change. If you have a record in your BaseClass table only, that entity is and will always be a MyBaseClass.
If entities can change their "type", you shouldn't use inheritance but composition.
How can I map this:
public class Customer
{
private IList<Order> _orders;
public IEnumerable<Order>
GetAllOrders()
{
return _orders;
}
}
On the project page are some samples but none is about this situation.
There is this sample:
// model
public class Account
{
private IList<Customer> customers = new List<Customer>();
public IList<Customer> Customers
{
get { return customers; }
}
}
// mapping
HasMany(x => x.Customers)
.Access.AsCamelCaseField();
But it assumes that Account has public field Customers and that scenario is different as mine. I tried some possible options but none works:
HasMany(x => Reveal.Propertie("_orders"))
Private fields works fine in simple property mapping but collection mapping
is quite different. Any idea? Thanks
The easiest solution is to expose your collection as a public property Orders instead of the GetAllOrders() method. Then your mapping is
HasMany(x => x.Orders)
.Access.AsCamelCaseField(Prefix.Underscore);
and your class is
public class Customer
{
private IList<Order> _orders = new List<Order>();
public IEnumerable<Order> Orders
{
get { return _orders; }
}
}
If that doesn't work for you, it is possible to map private properties using Fluent NHibernate's Reveal mapping.
Edited to add: Having just done this, the correct answer is:
HasMany<Order>(Reveal.Property<Customer>("_orders")) etc.
The collection must be exposed as a protected virtual property to allow proxying:
protected virtual IList<Order> _orders { get; set; }
This answer put me on the right track.
Thanks.
Your solution is fine. However, there could be situations(hypotetical) when you dont want to reveal your private collection. This mapping scenario is not explained in your linked post because there is difference between mapping simple propertie as descibed in that post and collection mapping. My attempt to use HasMany(x => Reveal.Propertie("_orders")) failed because of raised exception.
You can map a completely private collection using Reveal.Member(), but it has a specific and non-obvious restriction: the Expression that HasMany() accepts has to return either IEnumerable<TReferenced> or object.
For your class:
public class Customer
{
private IList<Order> _orders;
public IEnumerable<Order> GetAllOrders()
{
return _orders;
}
}
the following line will populate the _orders collection:
HasMany(Reveal.Member<Customer, IEnumerable<Order>>("_orders"));
//additional mapping behaviors
For completeness - the following line gives a compiler error:
HasMany(Reveal.Member<Customer, IList<Order>>("_orders"));