Map a HasMany relationship without any key column whatsoever - Where() only

39 views Asked by At

Conceptually I want to map a collection like this:

HasMany(x => x.Children).Where("some_column_name='foo'");

The default mode of HasMany() requires me to provide a KeyColumn which should exist on the child table, and whose value should be the identifier value of the parent. But, I don't want to do that. I want to provide the only and entire sql clause for the join to this child table relationship.

Is it possible?

I tried leaving off the KeyColumn from the mapping entirely. But, nHibernate adds something to the generated sql by default -- it seems to be some kind of identifier placeholder for the parent class. For instance if the parent class of this relationship is called "Parent" then nH generates sql like

select .... from children child0_ where ([my custom where clause, yay]) and child0_.Parent_id=?

I want it to stop adding any additional clauses to this join and use ONLY my custom where clause provided by the .Where() method of the mapping.

1

There are 1 answers

3
Firo On

I don't know the exact scenario so there might be an easier solution but this should work.

classes used

class Parent : Entity
{
    public virtual IList<Child> Children { get; protected set; } = new List<Child>();
}

class Child : Entity
{
    public virtual string ChildType { get; set; }
}

mapping

class ParentMap : ClassMap<Parent>
{
    public ParentMap()
    {
        Id(x => x.Id);

        HasMany(x => x.Children)
            .PropertyRef(nameof(ChildType))
            .KeyColumn("ChildType")
            .Cascade.All();

        Map(x => ChildType).Access.Using<ChildTypeGetter>().Formula("'foo'");
    }

    public string ChildType { get; set; }
}

class ChildTypeGetter : IPropertyAccessor, IGetter, ISetter
{
    public IGetter GetGetter(Type theClass, string propertyName) => this;
    public ISetter GetSetter(Type theClass, string propertyName) => this;

    public bool CanAccessThroughReflectionOptimizer => false;
    public string PropertyName => nameof(ParentMap.ChildType);
    public Type ReturnType => typeof(string);
    public MethodInfo Method => throw new NotImplementedException();

    public object Get(object target) => "foo";
    public object GetForInsert(object owner, IDictionary mergeMap, ISessionImplementor session) => Get(owner);
    public void Set(object target, object value) { }
}

class ChildMap : ClassMap<Child>
{
    public ChildMap()
    {
        Id(x => x.Id);

        Map(x => x.ChildType);
    }
}

Testcode

session.Save(new Child { ChildType = "foo" });
session.Save(new Child { ChildType = "foo" });
session.Save(new Child { ChildType = "bar" });
var parentId = session.Save(new Parent());
session.Flush();
session.Clear();

var parent = session.Get<Parent>(parentId);

Assert.Equal(2, parent.Children.Count);