This is definitely in the category of posts "write it down before you stop understanding the solution". Let's say that you're trying to track your servers. Servers are related to other servers. So, for instance, a live server has a corresponding DR server and a corresponding UAT server. Let's say also you have a simple table with two foreign key references that implements this relationship. You can implement this in fairly short order:
mapping.HasManyToMany(s => s.RelatedSites)
.Cascade.All()
.WithTableName("RelatedSite")
.WithChildKeyColumn("RelatedSiteId")
.WithParentKeyColumn("LiveSiteId")
.Inverse()
.FetchType.Join();
mapping.IgnoreProperty(s => s.RelatedSites);
The only problem with this is that it doesn't work. Now, if you automatically export your mapping files (and you should) you'll see that it's ignoring the key columns you specify. The automatic configuration is doing its own thing.
The problem is that you also added a foreign key convention by using the helper. Take a look at the code that implements this. You'll see that the Accept code looks a bit dodgy when you compare it to the Apply method. Instead, you can try the following code:
class ForeignKey : ForeignKeyConvention, IRelationshipConvention
{
private readonly Func<PropertyInfo, Type, string> standardRule;
public ForeignKey(Func<PropertyInfo, Type, string> standardRule)
{
this.standardRule = standardRule;
}
protected override string GetKeyName(PropertyInfo property, Type type)
{
return standardRule(property, type);
}
public bool Accept(IRelationship target)
{
if (target is IManyToManyPart) return base.Accept((IManyToManyPart)target);
return true;
}
public void Apply(IRelationship target)
{
if (target is IOneToManyPart) base.Apply((IOneToManyPart)target);
if (target is IManyToManyPart) base.Apply((IManyToManyPart)target);
if (target is IManyToOnePart) base.Apply((IManyToOnePart)target);
}
}
This code fixes the problem.
Anatomy of a Bug
This is a fix that raises as many questions as it answers. One is why Accept still doesn't do the obvious thing, the other is why I'm not going to submit this as a patch. The second is pretty easy to answer: this is nothing like a solution to the underlying problem. The first takes a bit longer...
If you make Accept do the obvious thing, it stops applying your foreign key rules anywhere. The problem is that the rules for one to many and many to many check to see if they've had their keys set already. And they already have, by the factory foreign key convention. So the user rule does the wrong thing in order to make up for that. So two wrongs nearly make a right, until you introduce a many to many part. The fix I've described above fixes it for me, but it's far from obvious that the underlying problem is fixable.
This is the second time I've found the factory conventions broke my code through no fault of my own (the other is the well documented, but still present, nullable enum bug) and I have to admit that I'm starting to wonder if the conventions code is more trouble than it's worth. Certainly the interaction between the unremovable factory conventions and your own conventions is at best hard to understand and at worst an unsolvable problem. Well, I'm still learning...
Technorati Tags:
Fluent NHibernate