views:

126

answers:

1

I have two tables in my database:

Wiki
    WikiId
    ...

WikiUser
    WikiUserId (PK)
    WikiId
    UserId
    IsOwner
    ...

These tables have a one (Wiki) to Many (WikiUser) relationship.

How would I implement the following business rule in my LINQ entity classes:

"A Wiki must have exactly one owner?"

I've tried updating the tables as follows:

Wiki
    WikiId (PK)
    OwnerId (FK to WikiUser)
    ...

WikiUser
    WikiUserId (PK)
    WikiId
    UserId
    ...

This enforces the constraint, but if I remove the owner's WikiUser record from the Wiki's WikiUser collection, I recieve an ugly SqlException. This seems like it would be difficult to catch and handle in the UI.

Is there a way to perform this check before the SqlException is generated? A better way to structure my database? A way to catch and translate the SqlException to something more useful?

Edit: I would prefer to keep the validation rules within the LINQ entity classes if possible.

Edit 2: Some more details about my specific situation.

In my application, the user should be able to remove users from the Wiki. They should be able to remove any user, except the user who is currently flagged as the "owner" of the Wiki (a Wiki must have exactly one owner at all times).

In my control logic, I'd like to use something like this:

wiki.WikiUsers.Remove(wikiUser);
mRepository.Save();

And have any broken rules transferred to the UI layer.

What I DON'T want to have to do is this:

if(wikiUser.WikiUserId != wiki.OwnerId) {
    wiki.WikiUsers.Remove(wikiUser);
    mRepository.Save();
}
else {
    //Handle errors.
}

I also don't particularly want to move the code to my repository (because there is nothing to indicate not to use the native Remove functions), so I also DON'T want code like this:

mRepository.RemoveWikiUser(wiki, wikiUser)
mRepository.Save();

This WOULD be acceptable:

try {
    wiki.WikiUsers.Remove(wikiUser);
    mRepository.Save();
}
catch(ValidationException ve) {
    //Display ve.Message
}

But this catches too many errors:

try {
    wiki.WikiUsers.Remove(wikiUser);
    mRepository.Save();
}
catch(SqlException se) {
    //Display se.Message
}

I would also PREFER NOT to explicitly call a business rule check (although it may become necessary):

wiki.WIkiUsers.Remove(wikiUser);
if(wiki.CheckRules()) {
    mRepository.Save();
}
else {
   //Display broken rules
}
A: 

This question has too many dependencies to answer well—the biggest of which is where you plan on enforcing business rules overall. In order of preference, you have to ask: Do you have a business rule layer? If not, do you have an independent data access layer? If not, are you using a data provider model? If not, then you're looking at enforcing this kind of thing (or handling the SqlException) wherever you're handling the configuration UI.

For something like a wiki, you probably don't need a hugely complex business rule engine simply because your problem domain is pretty well constrained already. Also, this sounds like the kind of rule that pretty much never changes so isolating it in a formal rules layer is a bit of overkill. As such, you're probably best off putting this kind of constraint in the data layer or data provider.

If you're using Linq to Sql, for example, you could mirror what you've done in SQL Server by making the OwnerId property in your model be a non-nullable property tied by a relationship to the WikiUser table. That'd enforce that the OwnerId property is populated (because it is non-nullable) and give you the unique constraint you are looking for.

Jacob Proffitt
My preference would be to do the validation within the LINQ objects. Prior to looking at this issue, I had planned on leaning on OnValidate.
AaronSieb
The main issue comes when I remove a WikiUser from the Wiki's child collection (in other words, when I delete a WikiUser). It is at that point that I receive the SqlException, and would rather receive something more targeted.
AaronSieb
Does that happen for all users or just the Owner? What do you want to happen when the owner is deleted? Should that operation be prevented or should the OwnerId simply be set null?
Jacob Proffitt
I have edited my question with more specific details.
AaronSieb