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…
Hi, it’s actually a replacement ForeignKey convention, so you just put it in your own code and use it. You don’t need to hack FNH, unlike some of my other posts. It’s one of the few extensibility points in FNH.However, as of RC1, there’s a new convention based way of doing many to many. I’d highly recommend doing that rather than this, because there’s still a number of issues to do with mixing in explicit stuff.I’m afraid that, ultimately, I’ve written off the convention stuff and gone back to the ClassMap stuff. That’s much more solid, but there’s still a couple of annoying edge cases (a many to many in which the keys are user types doesn’t really work, for instance).I love FNH, but sometimes I feel it doesn’t love me… 🙂
LikeLike