How to make a composite primary key bidirectional? - c#

My problem is in the title. I want to make a composite primary key as bidirectional. What I mean by bidrectional? Let me explain:
I have a friendship table. Primary keys are: SenderId and ReceiverId
In OnModelCreating:
builder.Entity<Friendship>()
.HasKey(i => new { i.SenderId, i.ReceiverId });
So my friendship entities have primary keys like {SenderId, ReceiverId}. But not like {ReceiverId, SenderId}. I want to make keys to be both ways. In this way, my friendship requests will be unique and I will not create duplicate friendship requests as follows:
Is this possible?
NOTE: I know how to check if there is a entry with those IDs.But I want to implement the database to reject the new entry with the same IDs in both ways.

You need a key that conforms to a canonical form, i.e. always appear in the same order.
For example, instead of having a ReceiverID and a SenderID, you'd have two neutrally named fields (e.g. ParticipantA and ParticipantB) plus a constraint that ParticipantA must be less than ParticipantB. This way, you can set a constraint so that each pairing of IDs can only appear once, regardless of who is sending and who is receiving.
You'd then need to add a column that specifies direction of the relationship, e.g. who is the sender or if it is bidirectional.
Thus instead of
SenderID ReceiverID
-------- ----------
1111 2222
4444 3333
1234 5678
5678 1234
You'd have
ParticipantA ParticipantB Sender
------------ ------------ ---------
1111 2222 A
3333 4444 B
1234 5678 BIDIRECTIONAL

As #John Wu points out, you need a composite key where the containing key values are always in order, so (1, 2) and (2, 1) are both mapped to (1, 2), thus treated as equal.
However the proposed implementation requires too much changes to the database model and how you work with it. Also prevents having natural User.Senders and User.Receivers collection navigation properties if needed to obtain such information for a User.
So instead of modifying the existing primary data model, what you need is unique constraint (index) on a "normalized" composite key (User1Id, User2Id), where (in preudo code) User1Id = Min(SenderId, ReceiverId) and User2Id = Max(SenderId, ReceiverId).
The implementation of this is database specific. For SqlServer it can be implemented by creating two computed columns and then create unique constraint (index) on them, e.g.
// the two shadow properties mapped to the computed columns
modelBuilder.Entity<Friendship>()
.Property<Guid>("UserId1")
.HasComputedColumnSql("case when SenderId < ReceiverId then SenderId else ReceiverId end");
modelBuilder.Entity<Friendship>()
.Property<Guid>("UserId2")
.HasComputedColumnSql("case when SenderId < ReceiverId then ReceiverId else SenderId end");
// the unique index using them
modelBuilder.Entity<Friendship>()
.HasIndex("UserId1", "UserId2")
.IsUnique();

Related

sync framework and syncing multiple client tables to one server table

Is it possible to sync multiple client tables to one server table? The issue I see it with the primary key being duplicated in the multiple clients. Is there a work around?
I tried to sync without the primary key but seems sync framework needs a key for it to work.
Or is my best bet to create multiple tables on the server and then create a view to combine the data without the primary key column?
An easy way to avoid such duplicates is to use:
a natural key
a PK providing service (you'll need some sort of number provider)
not recommended: a randomly generated PK (instead of the usual +1) (a guid works sometimes but is very tricky)
A natural key differs from the auto generated number: it's often an intrinsic property of the "thing" you are trying to store. Think of something like a license plate on a car.
By using this property, you can ensure that when you have this key, you have the same record.
Keep in mind, there are always some exceptions, even with license plates.
As for now:
You might be able to drop the insert identity, and reassign some numbers. But that will break the PK server / client relation (your server will have completely different PK than the clients). Over time this can give you a big headache.
An other way is to use a composite key:
Use your PK, but add a column like ClientID or ClientLocation, create a composite PK from both (not sure if the auto numbering will accept this). This might be the easiest option.
For the latter, in this example you can see that the combined key is unique:
//Client 1)
|---composite PK---|
| ID | ClientID |
|------|-----------|
1 cust1
2 cust1
3 cust1
//etc
//Client 2)
|---composite PK---|
| ID | ClientID |
|------|-----------|
1 cust2
2 cust2
3 cust2
//etc
//Server)
|---composite PK---|
| ID | ClientID |
|------|-----------|
1 cust1
1 cust2
2 cust1
2 cust2
3 cust1
//etc

Unique keys in N:M Relation SQL Server & C#

I have a table called cars and one called roads
Roads: Cars:
Name Road_Id Owner Car_Id
------------------- ---------------------
roade45 1 Hugo 1
roade20 2 Eson 2
roade10 3 Karl 3
I need to create an N:M relation between these (one can can drive on many roads, one road can have many cars).
It is a dumb example, but it needs to be an N:M relation.
I do this relation by creating an other table called cars_roads with these columns
Road_id | Car_id | uniqueValue
My problem is now that I have no idea on how to get the uniqueValue to take form, this value will be used to prevent doubles from occurring, eg. the same car is added to the same road a second time.
My table is created using this code:
CREATE TABLE [dbo].[Tbl_cars_roads]
(
[Road_id] INT NOT NULL,
[car_id] INT NOT NULL,
CONSTRAINT [PK_Tbl_Road_id] FOREIGN KEY ([Road_id]) REFERENCES [dbo].[Tbl_Cars] ([Road_id]),
CONSTRAINT [PK_Tbl_car_id] FOREIGN KEY ([car_id]) REFERENCES [dbo].[Tbl_Roads] ([car_id])
)
How can I add an uniqueValue key and make it work as explained to this code?
This should make unique group you want.
CONSTRAINT [UQ_CarRoads] UNIQUE NONCLUSTERED
(
[Road_id] ASC,
[car_id] ASC
)
Just add the following code after your last constraint:
CONSTRAINT [UK_Road_Car] UNIQUE ([Road_id], [car_id])

One foreign key in one table for 2 different fields

Is there a way for me to create only one foreign key in one table that refers to 2 different fields in that same table
example:
table: family
ID | bird | bird_mom | bird_dad
1___X_____2_______3
2____A____4________5
3____B
4____M
5____N
ID is primary and autoincrement and also foreign key for "bird_mom" and "bird_dad" field.
So when I change entries X,A,B,M,N I dont need to change it everywhere, but it will always be recognized by foreign key-ID
Typically, "foreign key" is the name for the child field that points to the parent, so you would have two foreign keys:
CREATE TABLE family (
ID INTEGER PRIMARY KEY,
bird TEXT,
bird_mom INTEGER REFERENCES family(ID),
bird_dad INTEGER REFERENCES family(ID)
)

Having trouble creating a one to 'zero or one' relationship using Entity Framework

I have two tables:
CREATE TABLE Order (
orderId INTEGER IDENTITY NOT NULL,
PRIMARY KEY (orderId)
)
CREATE TABLE OrderAdditionalDetails (
additionalDetailsId INTEGER IDENTITY NOT NULL,
orderId INTEGER NOT NULL,
PRIMARY KEY (additionalDetailsId),
FOREIGN KEY (orderId) REFERENCES Order(orderId)
)
I have a Foreign key (FK_OrderAdditionalDetails_Order) declared on the OrderAdditionalDetails table, on the orderId field. I also have a 'unique' constraint on the orderId field in the OrderAdditionalDetails table. The idea is that each 'order' will have zero or one entries in the 'OrderAdditionalDetails' table.
This all picked up by the entity framework model file, however when I try to create the Navigation property, it only lets me declare a 1 to many relationship. The error I get is as follows:
Running transformation: Multiplicity is not valid in Role 'OrderAdditionalDetails' in relationship 'FK_OrderAdditionalDetails_Order'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be *.
I'm really not sure what this means - googling the error did not prove helpful. Can anybody shed some light on what I am doing wrong?
In your OrderAdditionalDetails table, remove the additionalDetailsID column and make the orderID the CLUSTERED PRIMARY KEY. Keep the FOREIGN KEY you already have. That is the right way to implement this.
There is not only no value added by the additionalDetailsId column, it makes things worse by taking more space in the table. The orderID is already a sufficient key; you need no secondary artificial key that is nothing but a surrogate for orderID.
Your Foreign Key must be defined as UNIQUE in order to enforce a One-To-Zero-Or-One relationship.
Maybe try something like this:
CREATE TABLE OrderAdditionalDetails (
additionalDetailsId INTEGER IDENTITY NOT NULL,
orderId INTEGER NOT NULL UNIQUE,
PRIMARY KEY (additionalDetailsId),
FOREIGN KEY (orderId) REFERENCES Order(orderId)
)
See Also: Implementing one-to-zero-or-one relation in SQL Server
I was trying to associate a table with a view of itself plus some other fields. (There is a very good reason for this that has nothing to do with the answer)
What cause the same error was there was more than one key field on the view. Even though I had specified the fields involved in the association it wanted both to be the only key fields for a 1 to 1 to work.
I also set the key field to be Distinct in the view, but I did that before I removed the key attribute of other fields, so it may ,or may not, be necessary.

Entity Framework Many-To-8 Relationship?

This is likely a much broader SQL topic than Entity Framework, and I'm very much a newbie in both these arenas, but I'll ask it in terms of Entity Framework.
I would like to enforce a many-to-8 relationship. My setup is this:
A PersonGroup needs 8 (unique) Persons.
A Person can be in many different PersonGroups.
The order of the PersonGroup matters (the first needs to remain first, etc).
Easy access to all people in a PersonGroup and all PersonGroups a Person is in.
I've tried the following:
1) Add 8 1..many associations between Person and PersonGroup. I can certainly not have more than 8 Persons per group using this solution. However, to find all groups a person is in I need to iterate over 8 variables in the Person field, which is clunky.
2) Add 8 ids to PersonGroup that match up with a Person. Once again, I can guarantee only 8 persons per group, but there is no automatic link back through the association of Person->PersonGroup. I now need to be sure to add it to two places.
3) Just do a many...many relationship and handle it in code. There are two problems with this: I cannot guarantee only 8 persons per group, and I'm unsure if I can assure the order remains the same.
So, which is the best, or what solution am I missing?
An n:m relationship with a "catch":
Person
------
PersonId
PRIMARY KEY (PersonId)
PersonGroup
-----------
GroupId
PRIMARY KEY (GroupId)
Belongs
-------
GroupId
PersonId
Ordering
PRIMARY KEY (GroupId, PersonId)
FOREIGN KEY (GroupId)
REFERENCES PersonGroup (GroupId)
FOREIGN KEY (PersonId)
REFERENCES Person (PersonId) --- all normal up to here
UNIQUE KEY (GroupId, Ordering) --- the "catch"
CONSTRAINT Ordering_chk --- ensuring only up to 8 persons
CHECK Ordering IN (1,2,3,4,5,6,7,8) --- per group
You should make sure that the CHECK constraint is available in the SQL engine you'll use (MySQL for example would trick you into believing it has such constraints but it simply ignores them. SQL-Server does not return an error but happily adds a NULL in the checked column if you try to insert one.)
There is a limitation to this approach. The Ordering field has to be NOT NULL because if it is NULL, more than 8 rows (with NULL there) could be inserted (except for SQL-Server which would allow you only up to 9 rows, eight with values and one with NULL.)
To ensure maximum of 8 rows and NULLs in the Ordering, you could make a more complex constraint like the one described in MSDN site, CHECK Constraints (if your RDBMS has such feature) but I'm not at all sure on the performance of such a beast:
CREATE FUNCTION CheckMax8PersonPerGroup()
RETURNS int
AS
BEGIN
DECLARE #retval int
SELECT #retval = CASE WHEN EXISTS
( SELECT *
FROM Belongs
GROUP BY GroupId
HAVING COUNT(*) > 8
)
THEN 0
ELSE 1
END
RETURN #retval
END;
GO
ALTER TABLE Belongs
ADD CONSTRAINT Ordering_chk
CHECK (CheckMax8PersonPerGroup() = 1 );
GO
The constraint could alternatively be created as a FOREIGN KEY to a reference table with 8 rows. (If you use MySQL, that's the only way to have the equivalent of CHECK.)
A variation would be to use the (GroupId, Ordering) as the Primary Key and not have any constraint on the (GroupId, PersonId) combination. This would allow for a Person having multiple positions in a Group (but still up to 8) .
Many-to-many seems ok to me. You can easily make sure there are no more than 8 persons per group by implementing triggers. Also, you can add order column to this table if you think it's important for your logic.

Categories

Resources