Prisma: Create or Connect Records in Explicit Many-to-Many Relations - orm

In my Prisma Schema, I'm finding it difficult to undertand how to to create records in case of explicit many-to-many relations.
I have the following schema. Basically it represents Lists of Books. Users can Create Lists of Books.
A user can create a New list and then add books to this list along with their own notes. The Book Model is pure and contains standard book information.
The extra model is required because the user who is adding the book to the list can add his own notes about the book.
model List {
id Int #default(autoincrement()) #id
title String
slug String?
content String?
published Boolean #default(false)
author User? #relation(fields: [authorId], references: [id])
authorId Int?
books BooksInLists[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
model BooksInLists {
list List #relation(fields: [listId], references: [id])
listId Int // relation scalar field (used in the `#relation` attribute above)
book Book #relation(fields: [bookId], references: [id])
bookId Int // relation scalar field (used in the `#relation` attribute above)
##id([listId, bookId])
adder User? #relation(fields: [adderId], references: [id])
adderId Int?
notes String?
}
model Book {
id Int #id #default(autoincrement())
name String
lists BooksInLists[]
curator User? #relation(fields: [curatorId], references: [id])
curatorId Int?
bookDescription String?
}
model User {
id Int #default(autoincrement()) #id
name String?
email String? #unique
lists List[]
books Book[]
booksinlists BooksInLists[]
##map(name: "users")
}
The queries that I want to be able to do.
While updating a list, I should be able to add a new book to the list. This should create the new book, and also allow me to add a new record in BooksInLists model along with the 'notes' field.
While updating a list, I should be able to add/connect an existing book to the list. This will allow me to add a new record in BooksInLists model along with the 'notes' field.

It will be something like that:
prisma.booksInLists.create({
data: {
list: {
connect: {
id: 99
},
},
book: {
create: {
name: 'Young Lions'
}
}
}
})
However I see flaws in database schema. Model BooksInLists connects Books and List, so you don't need adder relation. In turn in Book model you shouldn't add curator relation because it's many to many relation. You have to use junction table usersBooks that connects User and Book tables.

Related

Cascading delete with sqlite isn't working with a join table

If you have a table that's just a hash of a many-to-many relationship between two other tables, like :
type User struct{
UserId string
...
}
type Roles struct{
Level string
...
}
where UserId and Level are PrimaryKeys.
The "join table" was originally:
type User_Role struct{
UserId string
Level string
}
How can you build that in gorm with cascading foreign key relataionships, et al.
I've been trying to piece it together from their docs, and different sources, but when I delete either the user or role, the join table still has the records.
Currently, this is what I've got:
type User_Roles struct {
User_Id string `gorm:"primaryKey; not null;ForeignKey:user_id;references:users.user_id,constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Level string `gorm:"primaryKey; not null;ForeignKey:level;references:roles.level,constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
When I had fields in the struct for User and Role sqlite created the table with every field from both, doing it this way created the table with the right fields, but the cascading isn't working.
Foreign key checks are disabled in SQLite by default, you need to enable them:
db.Exec("PRAGMA foreign_keys = ON")
If you specify the foreign key relations like this, they should work as you'd expect:
type User struct {
UserId string `gorm:"primaryKey;not null;"`
UserRoles []User_Roles `gorm:"foreignKey:UserId;references:UserId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
type User_Roles struct {
UserId string `gorm:"primaryKey;not null;"`
Level string `gorm:"primaryKey;not null;"`
}
i.e. if you delete a user, its associations will be removed.

How can I adjust prisma scheme to 'autofil'

I just started using prisma and was wondering whether you could perform an 'autofill'.
For instance: I have a leaderboard, and whenever I list down the teamID, the teamName column gets filled up automatically.
A piece of the schema is as follows.
model LeagueTable {
id Int #id #default(autoincrement())
competitionId Int
teamId Int
played Int
won Int
drawn Int
lost Int
points Int
goalsFor Int
goalsAgainst Int
goalDifference Int
tname String
competition Competitions #relation("competition-lt", fields: [competitionId], references: [id])
team Teams #relation("team", fields: [teamId], references: [id])
}
model Teams {
id Int #id #default(autoincrement())
name String #unique
matchesAsAway Fixtures[] #relation("awayTeam")
matchesAsHome Fixtures[] #relation("homeTeam")
leagueTable LeagueTable[] #relation("team")
}
I was thinking of adding a relation but at the same time I am trying to normalise the scheme as a possible.
Prisma Studio Leaguetable Preview
As you can see the tName column is empty, and I would need to fill it up manually. Is there a way to have it filled up when inserting the teamID
The only way to accomplish this in Prisma is to add another relation as you mentioned.
It might be possible with custom SQL triggers (like before insert), but you would be well outside the Prisma happy path.

Room insert into one-to-many relationship

When trying to use intermediary classes to model entity relationships in Room, I have run into an issue. Whilst documentation describes how to get from a one-to-many relationship, it does not describe how to insert.
I'm assuming this cannot be done automatically, therefore we need a query to insert the parent, retrieve the ID for the parent and assign it to the child's foreign key, and then insert the child.
The problem is that I am unsure where to put such a query. If I include it in my DAO, then I will have to include superflous methods for inserting the child. If I include it in my Repository, this makes testing very difficult (if not impossible).
Does anyone know how to resolve this?
I'm assuming this cannot be done automatically, therefore we need a query to insert the parent, retrieve the ID for the parent and assign it to the child's foreign key, and then insert the child.
The first assumption is correct, that is that you have to supply the id of the parent (otherwise how is to know the parent).
However the second assumption that you have to query the parent is not always the case and not so in the scenario you describe. If when inserting a parent then the id is returned if using the convenience #Insert as a Long or an array of Longs (if inserting multiple Parents).
For example, say you have :-
#Entity
data class Parent(
#PrimaryKey
var id: Long? = null,
var other: String
)
and
#Entity
data class Child(
#PrimaryKey
var id: Long? = null,
var parentId: Long,
var otherdata: String
)
and an #Dao annotated class with :-
#Insert
fun insert(parent: Parent): Long
#Insert
fun insert(child: Child): Long
Then you can use the following, without having to query the Parent:-
var lastParent = dao.insert(Parent(other = "Parent1 other data"))
dao.insert(Child(parentId = lastParent, otherdata = "Child1 other data"))
dao.insert(Child(parentId = lastParent, otherdata = "Child2 other data"))
// Insert a Child with it's Parent together
dao.insert(Child(
parentId = dao.insert(Parent(other = "Parent2 other data")),
otherdata = "Child3 other data"
))
note even if you define the id's as Int, a Long is returned when inserting.
It is incorrect to use Int for an id as SQLite stores the id as a 64bit signed integer which is to large for an Int.
However, issues would not occur until the id reached a value that is too large for an Int (32bit signed) i.e. greater than 2,147,483,647.

NHibernate SaveOrUpdate without primary key

The Situation
I've got a database table that is mapped via NHibernate (3.3.3-SP1). The application is running on .NET4.0 and the mapping is done via FluentNHibernate (1.4.0).
CREATE TABLE Movies
(id INT PRIMARY KEY,
yearPublished DATETIME NOT NULL,
name NVARCHAR(500) NOT NULL,
description NTEXT NOT NULL)
The data would be something like this:
id | yearPublished | name | description
---+---------------+------------------------+--------------------------------------------
1 | 1968 | 2001: A Space Oddyssey | An epic drama of adventure and exploration
The Problem
I'm creating new entities of this table and want to avoid adding more than one entity for the same real world thing. I know that there is Session.SaveOrUpdate and that there is also a way to make it work with composite and natural ids but that's not really what I want since my entities actually have a primary key and I really only need the composite key for making sure that no duplicates are in the DB.
var movie = new Movies
{
yearPublished = 1968,
name = "2001: A Space Oddyssey",
description = "An awesome journey to Jupiter"
};
// Behavior right now:
// Adds a new movie besides the fact that
// the movie is already in the database
// but now has two entries
session.SaveOrUpdate(movie);
Assert.IsTrue(movie.id == 2 && movie.description == "An awesome journey to Jupiter");
// What I really want is to be able to define what
// makes an object unique other than the primary key;
// in this scenario it should look for a combination
// of "yearPublished" and "name"
session.MyAwesomeSaveOrUpdate(movie);
Assert.IsTrue(movie.id == 1 && movie.description == "An epic drama of adventure and exploration");
Is this functionality in place in NHibernate (e.g. through a custom mapping) or do I have fetch the candidates from the DB and do it by hand?
Thanks!
I solve this by adding a unique constraint on the natural key fields in the database and using an exception converter to convert the SQL Server exception into one my application can handle.
public class SqlServerExceptionConverter : ISQLExceptionConverter
{
public Exception Convert(AdoExceptionContextInfo adoExceptionContextInfo)
{
var sqlException = adoExceptionContextInfo.SqlException as SqlException;
if (sqlException != null)
{
// 2601 is unique key, 2627 is unique index; same thing:
// http://blog.sqlauthority.com/2007/04/26/sql-server-difference-between-unique-index-vs-unique-constraint/
if (sqlException.Number == 2601 || sqlException.Number == 2627)
{
// my custom exception
return new UniqueKeyException(sqlException.Message, sqlException);
}
}
return adoExceptionContextInfo.SqlException;
}
}
Another approach I can think of is to query the database for a matching record before the insert but that's not foolproof because a record could be inserted between the select and your insert.

JPA-how to Add record to master table and child table?

I am having two tables One is a Master table called TRANSACTION and second is record of the transaction this table is called TRANSACTION_RECORD.
TRANSACTION
CREATE TABLE `e3_transaction` (
`transactionid` bigint(20),
`transactiontype` varchar(10),
`transactionstatus` varchar(10),
PRIMARY KEY (`transactionid`)
);
TRANSACTION_RECORD
CREATE TABLE `e3_as2805msg4` (
`transectionid` bigint(20),
`messageType` int(4),
`cardAcceptorName` varchar(40),
`adNational` varchar(1000),
`adPrivate` varchar(1000),
KEY `transectionidFK` (`transectionid`),
CONSTRAINT `transectionidFK` FOREIGN KEY (`transectionid`) REFERENCES `e3_transaction` (`transactionid`)
);
It will have one to one mapping between Transaction and transaction record. It means one transaction can have only one record. I have kept this table separately for some reasons. So my class will look like this:
#Entity
#Table(name = "e3_transaction")
public class Transaction {
#Id
#GeneratedValue(generator = "assigned-by-code")
#GenericGenerator(name = "assigned-by-code", strategy = "assigned")
#Column(name = "transactionid", unique = true, nullable = false)
private Long transactionid;
#Column(name = "transactiontype", nullable = false, length = 10)
private String transactiontype;
#Column(name = "transactionstatus", nullable = false, length = 10)
private String transactionstatus;
#oneToOne
private TransactionRecord record;
}
I want to persist both objects at a same time. when I persist a TRANSACTION, TRANSACTION_RECORD should be persist in it's table. Is there any way to do this ?
You can change the table structure if you want. Only thing i need it TWO tables.
Works with
#OneToOne(cascade=CascadeType.ALL)
#MapsId
private TransactionRecord record;
TransactionRecord must have an #Id of the same type as Transaction with no value generation.
Tried with Hibernate as JPA 2.0-provider.
There are a few options to map this, but it looks like you are handling it with two separate entities. As both entities share the same primary key value, your reference mapping will need to change based on which entity you wish to have controlling the pk value generation - as it stands, the e3_transaction.transactionid field is being set by two separate mappings; the transactionid long and the TransactionRecord record reference.
If you wish to use the #MapsId as is suggested in the other answer, you will need to move your #GeneratedValue code to the TransactionRecord entity, as the JPA provider will use the value in the referenced TransactionRecord to set the transactionid attribute and the database field. This is a simple elegant solution, but you can also remove the Long transactionid attribute from Transaction and just mark the record reference with #Id (instead of #MapsId). The long transactionId value within TransactionRecord would still be used as Transaction's id for EntityManager getReference and find calls.
A different option that allows keeping the #GeneratedValue on the transactionid within Transaction is to define the #JoinColumn annotation on the record reference and specify that the field is insertable=false, updatable=false. You then need to modify the TransactionRecord so that it has a back relationship to the Transaction so that it can pull the transectionid value from the Transaction instance to use as its id. This can be accomplished by simply marking the relationship with #ID though.