1

I'm used to using identity columns and having pk's generated for me. I'm wondering, is there any way to scope an identity column or otherwise compute it to be unique within a foreign key?

For example, using identity column a table might look like this:

| UserId | WidgetId | <-- Table Widget, WidgetId is identity column & pk
---------------------
|      1 |        1 |
|      1 |        2 |
|      1 |        3 |
|      2 |        4 |
|      2 |        5 |
|      2 |        6 |

What if I want to achieve something more like the following:

| UserId | WidgetId | <-- Table Widget, both UserId and WidgetId compose the pk
---------------------
|      1 |        1 |
|      1 |        2 |
|      1 |        3 |
|      2 |        1 |
|      2 |        2 |
|      2 |        3 |

I know EF allows for database-generated column values, like identity and computed. My question is, how might one achieve a computed column like this?

I realize it can be done in the application, by finding the highest WidgetId a user has and incrementing by one. But this doesn't feel like it's the right way.

I imagine it might also be possible to do this with a trigger, but adding a trigger to the db from the DbContext would place a dependency on the RDBMS, right?

Is there a better way, something that takes less of a dependency on which backend EF is using?

Update

To be more clear, in the model above, there is an identifying relationship between Widget and User on purpose. UserId is the primary key of the User table, and by having the FK to User as part of Widget's PK, this means that Widgets are never shared between Users. When a User is deleted, all of the Widgets get deleted along with it. Querying Widgets by WidgetId alone makes no sense; To query for a particular Widget, both the UserId and WidgetId parameters are necessary.

I'm exploring this because I recall reading in Evans DDD that non-root entity id's in an aggregate only need to be unique within the aggregate. The above is an example of this case:

ENTITIES other than the root have local identity, but that identity needs to be distinguishable only within the AGGREGATE, because no outside object can ever see it out of the context of the root ENTITY. (Domain-Driven Design, Evans)

Update 2

After Gorgan's answer, I have implemented something like the following in my widget factory class. Is this the correct approach to achieve the result desired above?

public class WidgetFactory : BaseFactory
{
    private object _sync = new object();

    internal WidgetFactory(IWriteEntities entityWriter) : base(entityWriter)
    { 
        // my DbContext class implements the IWriteEntities interface
    }

    public Widget CreateOrUpdate(User user, int? widgetId, string prop1, 
        bool prop2, string prop3)
    {
        Widget widget = null;
        if (!widgetId.HasValue)
        {
            // when widgetId is null do a create (construct & hydrate), then
            EntityWriter.Insert(widget); // does DbEntityEntry.State = Added
        }
        else
        {
            // otherwise query the widget from EntityWriter to update it, then
            EntityWriter.Update(widget); // does DbEntityEntry.State = Modified
        }

        // determine the appropriate WidgetId & save
        lock (_sync)
        {
            widget.WidgetId = widgetId.HasValue ? widget.WidgetId
                : EntityWriter.Widgets.Where(w => w.UserId == user.Id)
                    .Max(w => w.WidgetId) + 1;
            EntityWriter.SaveChanges(); // does DbContext.SaveChanges()
        }

        return widget;
    }
}

Widget

I suppose I should not have disguised this term. The actual entity is WorkPlace. I am building an app to keep track of where I work during the year, as I have to submit an itinerary with my municipal tax refund form every year. When done, I plan to publish it to the cloud so others can use it for free. But of course I want their WorkPlaces to be completely isolated from mine.

Users are automatically created for every visitor using anonymous identification / Request.AnonymousID (there will be a register feature later, but it is not necessary to try out a demo). The web app will have restful url's like /work-places/1, /work-places/2, etc. Because every request is guaranteed to have a user, the user id need not be identified in the url. I can identify users from Request.AnonymousID when nothing is present in User.Identity.Name.

If WorkPlaceId was an identity column, my controller actions would first need to check to make sure the user owns the workplace before displaying it. Otherwise I could hack the URL to see every WorkPlace that every user has set up in the system. By making the WorkPlaceId unique only for the user, I need not worry about it. The URL /work-places/1 will display entirely different data to 2 different users.

danludwig
  • 46,965
  • 25
  • 159
  • 237
  • If you're using EF 5 you should tag it with that. – Yuck Mar 15 '12 at 19:40
  • Awesome, last time I checked it had not been created yet. Thanks for the heads up. – danludwig Mar 15 '12 at 19:46
  • Yes, that is about it, but I would love to know more about what that widget represents, as this seems unnatural and like there has to be a better way to model it. – Goran Obradovic Mar 16 '12 at 21:10
  • I should not have disguised Widget... I meant it as "any arbitrary entity that is unique within a user." In the app, the actual entity is `WorkPlace`. I have updated my question. Thanks for your help. – danludwig Mar 16 '12 at 21:33
  • Yes, this is candidate for third paragraph, I have clarified it :) – Goran Obradovic Mar 16 '12 at 21:56
  • I have read your update, and I really think this is a complification. Giving WorkPlace its own lifetime is not necessary. It contains just 3 properties: Name, IsTaxable, and Color. A user should be able to edit any of these fields without affecting any other user, current or future. Basically, you can get refunded a portion of tax for days that you do not work in the city. The people will know which addresses they work at, and whether or not they are taxable. It may help if you read the actual tax refund form: http://www.cincinnati-oh.gov/cityfinance/downloads/cityfinance_pdf44827.pdf – danludwig Mar 16 '12 at 22:12
  • Place has its own lifetime, worked you there, or not, and you can model it like that, or calculate some imaginary id in code for every WorkPlace in that Place. You are trying to model it different, missing that part completely, and that is why you complicate your WorkPlace table. Weak entity PK should be comprised of identities of strong entities it depends on. Even if people can change Name, IsTaxable and Color, it is not reason for Place not to exist, if I have a name for MY WorkPlace => it goes to WorkPlace table, no problem. But place ID or ZIP code are same for all users. – Goran Obradovic Mar 16 '12 at 22:40
  • If you think that Place is complication, why you complicate with workplaceid? There is no need for workplaceid if it's identity is based on userid and name of workplace. – Goran Obradovic Mar 16 '12 at 22:49
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/8983/discussion-between-danludwig-and-goran-obradovic) – danludwig Mar 16 '12 at 22:51

2 Answers2

1

Purpose of identity column is to have some ID in table when you need column that will be unique no matter what other column values are, and sql server supports auto-generation for only one identity column in table (no multiple or complex primary auto-generated keys).

If your every widget is different (even when same widget is used by more users), then this primary key makes sense, but it cannot be computed in sql server (except if you use stored procedure or other db programmability for insert). And you can only read autogenerated column with ef (see How should I access a computed column in Entity Framework Code First?).

If you, however have more widget types (in this example widget types 1, 2 and 3 EDIT: WidgetId could be FK for table WidgetType where Id is autogenerated, so you would have PK comprised from 2 auto-generated ID's), you could make WidgetType (or WidgetInstance, WidgetSomething, whatever, entity that describes part of widget not affected by user) table and have autogenerated ID in it, and use it in this table as FK and part of primary key. That would allow you to have both UserId and WidgetId autogenerated and existing in moment of insert to this table.

Community
  • 1
  • 1
Goran Obradovic
  • 8,951
  • 9
  • 50
  • 79
  • I think this may be the answer, but you kind of lost me on the 3rd paragraph. I have updated the question to clarify. If such a `WidgetId` value must be computed, I hear you saying it must happen at the EF layer or higher, unless I want to create a sproc/trigger (I don't). So, what is a good approach? I could query the max widget id for a user, increment by one, set the property on the entity and SaveChanges... but even if the 2 lines are adjacent in the source code, isn't there a chance of concurrency conflict if 2 threads are each trying to insert a Widget for the same user at the same time? – danludwig Mar 16 '12 at 03:19
  • Yes, you need to have synced section between threads that will do the saving. I think that 3rd paragraph is best solution, but if one user can have 5 instances of "same" widget then is not applicable. Do you have anything shared between some widgets (i.e. name is "Clock" or "Weather") that is not changed by users? If not, and your only concern is to assure that widget does not exist without user, you could only make UserId FK NOT NULL, and when user is deleted, Widget must be deleted. – Goran Obradovic Mar 16 '12 at 06:33
  • The main reason I have chosen "Identifying Relationship" has to do with the way EF behaves when you try to remove an entity from a collection (i.e. `User.Widgets.Remove(User.Widgets.First())`. By default, it tries to set the FK to null. If the FK is not nullable, then EF throws an exception. However if the FK is part of the PK, then EF deletes the entity instead. – danludwig Mar 16 '12 at 15:28
  • You want your Widget to be _weak_ entity? Did you try to have both WidgetId and UserId as part of PK, but set WidgetId to auto-generated (identity)? I know that in that case WidgetId can be PK by itself, but that should not prevent you to mark both fields as PK :) – Goran Obradovic Mar 16 '12 at 21:13
  • I have augmented my question to be completely explicit. Sorry for the confusion. I really do not want to use identity column for any part of the dependent entity pk. – danludwig Mar 16 '12 at 21:38
  • If the WorkPlace PK was UserId + Name, then name would become immutable. Users must be able to edit/update workplace names. In the PDF, read the whole thing. When you work in more than 1 place, you must fill out form L-2-C and submit a chronological itinerary of places you worked and on which dates. – danludwig Mar 16 '12 at 23:00
0

I hate to oversimplify and apologize for doing so. Whenever this question comes up, it smells like a database design issue.

Widgets and users are both considered master data. The widget table should only contain attributes about the widget and the user table should only contain attributes about the user. If the widget is related to a user, it would probably be ok to include the userid in the widget table but the widgetid should be the identity and the userid would be an FK.

The linkage of the widget and user should be done outside of these master tables. If they are related to a bill/invoice, it's common to use a combination of an invoice header table (invoiceid, userid, datetime, total cost, total price...) and an invoice line table (invoicelineid, invoiceid, widgetid, unit cost, unit price, quantity...)

As the data volume grows, a little bit of normalization will ensure that your app will scale. Additionally, these strong relationships will help with data quality when it's time to turn this data into business intelligence.

brian
  • 3,635
  • 15
  • 17
  • Again, I think maybe I should have been clearer in my question. There is no grand scale to these 2 entities, this is a simple personal app that I am working on to become familiar with VS11, EF5, MVC4, etc. For these 2 entities, there is a definite `1 User .. n Widgets` relationship. I think you are suggesting a "gerund" table for mapping `m Users .. n Widgets`. In my model, a Widget can't exist without a user... UserId is part of its identity. I believe I have normalized it correctly, and adding a separate table to map the relationship sounds like a complication rather than a simplification. – danludwig Mar 16 '12 at 03:52