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 =>
{
....
}
Related
I can't seem to find a good example of what I want to do, using nHibernate mapping by code:
I have an object "Message" that has a list of "Organisms" and each "Organism" has a list of "Drugs". Please forgive my pseudo example below:
public class Message
List<Organism> Organisms;
public class Organism
List<Drugs> Drugs;
public class Drug
//create our tree structure
var message=new Message();
var drug = new Drug();
var organism = new Organism();
organism.Drugs.Add(drug);
message.Organisms.Add(organism); //now we have a message with one organism child with one drug drug
Using Bags in my class mappings, I am able to correctly save this message object, and have it persist correctly. The problem is when calling Get with the message ID to bring back the message. I am getting "collection is not associated with any session"
As for my mappers, my message has a bag of organisms, which have a bag of drugs.
Does anyone have an example of doing this type of thing with nHibernate mapping by code? I am missing something in my mappers...
you need to do a little more if you want bi-directional mappings.
So I would define a message property on the Organism class. And map that as a reference.
Add Add/Remove properties on the parent class for your collection class so that you can add the element to the list and to also add the this reference to your child object.
public class Message
{
public Int32 Id { get; set; }
public IList<Organism> Organisms { get; protected set; }
public Message()
{
Organisms = new List<Organism>();
}
public void AddOrganism(Organism organism)
{
if (Organisms.Contains(organism))
return;
organism.Message = this;
Organisms.Add(organism);
}
public void RemoveOrganism(Organism organism)
{
if (!Organisms.Contains(organism))
return;
Organisms.Remove(organism);
}
}
public class Organism
{
public Int32 Id {get;set;}
public Message Message { get; set; }
}
With regards to your mappings you need to set the Message.Organisms to a Bag and on the Organism.Message to a ManyToOne. For the next level down, just repeat this.
public class MessageMap
{
public MessageMap()
{
Bag(x => x.Organisms, map =>
{
map.Key(k =>
{
k.Column(col => col.Name("MessageId"));
});
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
},
action => action.OneToMany());
}
}
public class OrganismMap
{
public OrganismMap()
{
ManyToOne(x => x.Message, map =>
{
map.Column("MessageId");
map.NotNullable(false);
});
}
}
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 have the following classes:
public abstract class FooBase
{
public virtual Guid Id { get; set; }
}
public class FooTypeA : FooBase
{
public virtual string TypeAStuff { get; set; }
}
public class Bar
{
public virtual Guid Id { get; set; }
public virtual FooBase Foo { get; }
}
FooBase and FooTypeA are mapped using the table-per-class-heirarchy pattern.
Bar is mapped like this:
public class BarDbMap : ClassMap<Bar>
{
public BarDbMap()
{
Id(x => x.Id);
References(x => x.Foo)
.LazyLoad();
}
}
So when I load a Bar, its Foo property is only a proxy.
How do I get the subclass type of Foo (i.e. FooTypeA)?
I have read through a lot of NH docs and forum posts. They describe ways of getting that work for getting the parent type, but not the subclass.
If I try to unproxy the class, I receive errors like:
object was an uninitialized proxy for FooBase
I worked out how to avoid the exception I was receiving. Here is a method that unproxies FooBase:
public static T Unproxy<T>(this T obj, ISession session)
{
if (!NHibernateUtil.IsInitialized(obj))
{
NHibernateUtil.Initialize(obj);
}
if (obj is INHibernateProxy)
{
return (T) session.GetSessionImplementation().PersistenceContext.Unproxy(obj);
}
return obj;
}
Add a Self property to FooBase and use that to check the type:
public abstract class FooBase
{
public virtual Guid Id { get; set; }
public virtual FooBase Self { return this; }
}
Usage:
if (Bar.Foo.Self is FooTypeA) { // do something }
To get the "unproxied" type you could add a method like this to FooBase:
public virtual Type GetTypeUnproxied() {
return GetType();
}
When this method is invoked on a proxy the type of the underlying object will be returned.
However, from your description it seems you are trying to do this outside of the NHibernate session and that won't work with this strategy either. To invoke any method on the proxy where the call is proxied to the underlying object it needs to be instantiated and that can only happen within the NHibernate session since the actual type of the object is stored in the database (in a discriminator column for the table-per-class-hierarchy inheritance strategy). So my guess is that you need to make sure that the proxy is initialized before closing the session if you need to check the type later.
If the reason for lazy loading the Bar->FooBase relation is that FooBase (or a derived type) might contain large amounts of data and you are using NHibernate 3 you could use lazy properties instead.
All of my entities and value objects implement marker interfaces IEntity and IValueObject. I have set them up to be treated as components like so:
public override bool IsComponent(Type type)
{
return typeof(IValueObject).IsAssignableFrom(type);
}
public override bool ShouldMap(Type type)
{
return typeof(IEntity).IsAssignableFrom(type) || typeof(IValueObject).IsAssignableFrom(type);
}
Unfortunately, this does not seem to allow entities that have collections of value objects to be automapped as component collections. For example:
public class MyEntity : IEntity
{
public IList<MyValueObject> Objects { get; set; }
}
public class MyValueObject : IValueObject
{
public string Name { get; set; }
public string Value { get; set; }
}
Is there any way to define a convention such that, any time an IEntity has an IList of a type that implements IValueObject, it gets mapped as if I had specified:
HasMany(x => x.Objects)
.Component(x => {
x.Map(m => m.Name);
x.Map(m => m.Value);
});
What I don't want to do is have to manually do these overrides for every class and write out each property for the value object again and again.
Create a new class that inherits from HasManyStep (FluentNHibernate.Automapping.Steps).
Override the ShouldMap() method with something like :
return base.ShouldMap(member) && IsCollectionOfComponents(member)
Add your logic to :
public void Map(ClassMappingBase classMap, Member member)
{ ... }
Replace the default step with your new one :
public class MyMappingConfiguration : DefaultAutomappingConfiguration
{
public override IEnumerable<IAutomappingStep> GetMappingSteps(AutoMapper mapper, IConventionFinder conventionFinder)
{
var steps = base.GetMappingSteps(mapper, conventionFinder);
var finalSteps = steps.Where(c => c.GetType() != typeof(FluentNHibernate.Automapping.Steps.HasManyToManyStep)).ToList();
var idx = finalSteps.IndexOf(steps.Where(c => c.GetType() == typeof(PropertyStep)).First());
finalSteps.Insert(idx + 1, new MyCustomHasManyStep(this));
return finalSteps;
}
}
Note : You could also get the original source code of HasManyStep.cs and copy it to your project to introduce your custom logic.
I have two classes
public class Document
{
public virtual int Id { get; set; }
public virtual IList<File> Files { get; set; }
}
public class File
{
public virtual int Id { get; protected set; }
public virtual Document Document { get; set; }
}
with the following convention:
public class HasManyConvention : IHasManyConvention
{
public bool Accept(IOneToManyPart target)
{
return true;
}
public void Apply(IOneToManyPart target)
{
target.Cascade.All();
}
}
and these mapping overrides
public class DocumentMappingOverride : IAutoMappingOverride<Document>
{
public void Override(AutoMap<Document> mapping)
{
mapping.HasMany(x => x.Files)
.Inverse()
// this line has no effect
.Cascade.AllDeleteOrphan();
}
}
public class FileMappingOverride : IAutoMappingOverride<File>
{
public void Override(AutoMap<File> mapping)
{
mapping.References(x => x.Document).Not.Nullable();
}
}
I understand that I need to make an IClassConvention for Document to
change the cascade behaviour, however I can't get this to work!
If i do this:
public class DocumentConvention : IClassConvention
{
public bool Accept(IClassMap target)
{
return target.EntityType == typeof(Document);
}
public void Apply(IClassMap target)
{
target.SetAttribute("cascade", "all-delete-orphan");
}
}
I get: "The 'cascade' attribute is not declared."
If i do this:
public class DocumentConvention : IClassConvention
{
public bool Accept(IClassMap target)
{
return target.EntityType == typeof(Document);
}
public void Apply(IClassMap target)
{
target.HasMany<Document, File>(x => x.Files)
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
Then I get:
"Duplicate collection role mapping Document.Files"
so i added:
mapping.IgnoreProperty(x => x.Files);
to my document mapping, but then Files is always empty.
What am I doing wrong?
How can I override the cascade rule for a single HasMany relationship?
Thanks
Andrew
P.s. Sorry for the cross post with this but I need to get this solved asap.
I know this was forever ago (in computer time) and you might have already solved this. In case you haven't or someone else with a similar question sees this, here goes:
I think you need to create a class that implements IHasManyConvention. IClassConvention modifies an IClassMap (the <class> element) target. cascade is not a valid attribute for <class> so that accounts for the first error. On your second attempt, you were re-mapping the collection, resulting in the "duplicate collection" error.
IHasManyConvention targets an IOneToManyPart, upon which you should be able to call Cascade.AllDeleteOrphan() or just SetAttribute("cascade", "all-delete-orphan") if the former didn't work for some reason.
EDIT
Sorry, I missed that you already had a IHasManyConvention. Since you want to override your convention for just one type, you should just change the Accept method on your convention for that type. Instead of return true;, pull in what you had on your DocumentConvention:
return target.EntityType == typeof(Document);
I believe that OneToManyPart.EntityType references the containing entity type (i.e. Document).