ID 列を使用し、pk を生成することに慣れています。ID 列のスコープを設定したり、外部キー内で一意になるように計算したりする方法はありますか?
たとえば、ID 列を使用すると、テーブルは次のようになります。
| UserId | WidgetId | <-- Table Widget, WidgetId is identity column & pk
---------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
| 2 | 5 |
| 2 | 6 |
次のようなことを達成したい場合はどうすればよいですか。
| UserId | WidgetId | <-- Table Widget, both UserId and WidgetId compose the pk
---------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
EF では、id や計算など、データベースで生成された列の値を使用できることを知っています。私の質問は、このような計算列をどのように達成できるでしょうか?
ユーザーが持っている最大の WidgetId を見つけて 1 ずつインクリメントすることで、アプリケーションで実行できることがわかりました。しかし、これは正しい方法ではないように感じます。
トリガーを使用してこれを行うことも可能かもしれないと思いますが、トリガーを から db に追加するDbContext
と、RDBMS に依存することになりますよね?
EF が使用しているバックエンドへの依存を少なくする、より良い方法はありますか?
アップデート
より明確にするために、上記のモデルでは、Widget と User の間に意図的に識別関係があります。UserId は User テーブルの主キーであり、Widget の PK の一部として User への FK を持つことで、Widget がユーザー間で共有されることはありません。ユーザーが削除されると、すべてのウィジェットが一緒に削除されます。WidgetId だけでウィジェットをクエリしても意味がありません。特定のウィジェットを照会するには、UserId パラメーターと WidgetId パラメーターの両方が必要です。
Evans DDD で、集約内の非ルート エンティティ ID は集約内で一意である必要があるだけであると読んだことを思い出したので、これを調査しています。上記は、この場合の例です。
ルート以外の ENTITIES にはローカル ID がありますが、その ID は AGGREGATE 内でのみ識別可能である必要があります。これは、外部オブジェクトがルート ENTITY のコンテキストからそれを見ることができないためです。(ドメイン駆動設計、エヴァンス)
更新 2
Gorgan の回答の後、ウィジェット ファクトリ クラスに次のようなものを実装しました。これは、上記の望ましい結果を達成するための正しいアプローチですか?
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;
}
}
ウィジェット
この用語を偽装すべきではなかったと思います。実際のエンティティは WorkPlace です。毎年、市税還付申請書と共に旅程表を提出しなければならないので、その年のどこで働いているかを追跡するアプリを作成しています。完了したら、クラウドに公開して、他の人が無料で使用できるようにする予定です。しかしもちろん、私は彼らの WorkPlace を私のものから完全に分離したいと考えています。
ユーザーは、匿名の識別を使用してすべての訪問者に対して自動的に作成されます / Request.AnonymousID
(後で登録機能がありますが、デモを試す必要はありません)。Web アプリには、/work-places/1、/work-places/2 などの安静な URL があります。すべての要求にはユーザーが含まれていることが保証されているため、ユーザー ID を URL で識別する必要はありません。Request.AnonymousID
に何もないときからユーザーを識別できますUser.Identity.Name
。
WorkPlaceId が ID 列である場合、コントローラー アクションは、表示する前にユーザーが職場を所有していることを確認する必要があります。そうしないと、URL をハッキングして、すべてのユーザーがシステムに設定したすべての WorkPlace を確認できます。WorkPlaceId をユーザーに対してのみ一意にすることで、心配する必要はありません。URL /work-places/1 は、2 人の異なるユーザーにまったく異なるデータを表示します。