I have a feed table that contains id, body, created_at fields. When I send Post() on postman after Delete() method the id for the feed table auto_increments as if a record has not been deleted. I am unsure how to rectify this, I am using MySql database, nestjs and TypeORM for the backend.
feed controller.ts
#Controller("feed")
export class FeedController {
constructor(private feedService: FeedService) {}
#Post()
createNewPost(#Body() feedPost: HomeFeedDto): Observable<HomeFeedDto> {
return this.feedService.createPost(feedPost);
}
#Get()
allPosts(): Observable<HomeFeedDto[]> {
return this.feedService.getAllPosts();
}
//api delete method
#Delete(":id")
// delete home feed post by id
deleteFeedPost(#Param("id") id: number): Observable<DeleteResult> {
return this.feedService.deletePost(id);
}
}
This is just the way that auto incrementing columns work in a database. Once a record has been created that uses a particular id value it can never be used again, even if the record that owned it was deleted.
What would you expect to happen in the case where there were many records? If the current incrementing id was 1000 and then you deleted the record with id = 1 would you expect that the next time you inserted a record it would be given id = 1 again instead of id = 1001?
There are lots of practical reasons why re-using a previously issued id would be very bad for business logic especially if anyone who is a consumer of your API has a cached version of the old record.
If you really want to achieve this behavior you would have to look at writing custom functions either inside of the database or your API which check to see if any ids are missing from sequence and then manually assign your own IDs instead of letting the database do it. I would highly recommend you don't do this though as the behavior you're seeing is designed like that for a reason.
Related
I'm trying to save (insert) a new record in my DB but all i get is the error: Cannot update entity because entity id is not set in the entity.
What I'm currently doing:
return this.connection.transaction(entityManager => {
return entityManager.save(MyEntity, {/* payload without id */});
});
This is the only place in my codebase where this issue happen (transaction or not)
TypeORM tracks entities with meta-data and most likely the id is still in the meta-data or has gotten in the meta-data. To fix this pass it through the create function.
return this.connection.transaction(entityManager => {
// make sue payload is at this point already without id field else your problem will remain
const entity = entityManager.create(payload)
return entityManager.save(MyEntity, entity);
});
Most likely you are using a fetched entity as template, deleted the id, plus maybe some other settings and tried saving again.
Even though this might not be the exact way you got this error. This I see most commonly happen when this error occurs.
I'm very much a beginner when it comes to database relationships hence what I suspect is a basic question! I have two database tables as follows:
Projects
id
company_id
name
etc...
rfis
id
project_id (foreign key is id on the Projects table above)
Number (this is the column I need help with - more below)
question
The relationships at the Model level for these tables are as follows:
Project
public function rfi()
{
return $this->hasMany('App\Rfi');
}
RFI
public function project()
{
return $this->belongsTo('App\Project');
}
What I'm trying to achieve
In the RFI table I need a system generated number or essentially a count of RFI's. Where I'm finding the difficulty is that I need the RFI number/count to start again for each project. To clarify, please see the RFI table below which I have manually created with the the 'number' how I would like it displayed (notice it resets for each new project and the count starts from there).
Any assistance would be much appreciated!
Todd
So the number field depends on the number of project_id in the RFI table. It is exactly the number of rows with project_id plus one.
So when you want to insert a new row, you calculate number based on project_id and assign it.
RFI::create([
'project_id' => $project_id,
'number' => RFI::where('project_id', $project_id)->count() + 1,
...
]);
What I understood is that you want to set the value of the "number" field to "1" if it's a new project and "increment" if it's an existing project. And you want to automate this without checking for it every time you save a new row for "RFI" table.
What you need is a mutator. It's basically a method that you will write inside the desired Model class and there you will write your own logic for saving data. Laravel will run that function automatically every time you save something. Here you will learn more about mutators.
Use this method inside the "RFI" model class.
public function setNumberAttribute($value)
{
if(this is new project)
$this->attributes['number'] = 1;
else
$this->attributes['number']++;
}
Bonus topic: while talking about mutators, there's also another type of method called accessor. It does the same thing as mutators do, but just the opposite. Mutators get called while saving data, accessors get called while fetching data.
My models have both id and counter attributes. The id is a UUID, and the counter is an integer which is auto-incremented by the database.
Both are unique however I rely on id as the primary key. The counter is just a human-friendly name that I sometimes display to the user.
Immediately before an object is created a listener gives it a UUID. This works fine.
When the record is saved, MySQL increments the counter field. This works fine except that the copy of the object which I have in memory does not have the counter value. I can reload the object to find out what its counter is, but that would require another database query.
Is there a way to find the value of the counter without a specific database query? For example, is it returned as part of the response from the database when a record is created?
Few things:
Use create(array $attributes) and you'll get exactly what you want. For this having right, you have to ensure that $fillable array consists all attributes' names passed to create method.
You should use Observer on model instead of listener (most likely creating method).
Personal preference using Eloquent is that you should use id for id (increment field) and forget custom settings between models because by default it is what relations expect and so on
public function secondModels()
{
return $this->hasMany(SecondModel::class);
}
is pretty much no brainer. But for having this working best way would be (also following recommendations of this guy) FirstModel::id, SecondModel::id, SecondModel::first_model_id; first_models, second_models as table names. Avoiding and/or skipping this kind of unification is lot of custom job afterward. I don't say it can't be done but it is lot of non-first-time-successful work done.
Also, if you want visitor to get something other than id field name, you can make computed field with accessor:
/**
* Get the user's counter.
*
* #return string
*/
public function getCounterAttribute(): string
{
return (string)$this->id;
}
Which you call then with $user->counter.
Also personal preference of mine is to have most possible descriptive variable names so uuid field of mine would be something like
$table->uuid('uuid4');
This is some good and easy to make practice of Eloquent use.
Saying all this let me just to say that create() and save() will return created object from database while insert() shall not do it.
I have auto-increment ids as primary is all of my db tables, like users, orders etc. I do not want to expose these ids to end users, as they may iterate over IDs can get access to user details. Instead I want to use a 2-way maths function such that I can obfuscate and de-obfuscate an id without storing a DB mapping.
function obfuscate(id)
{
constSeed = 1203793
return (id*constSeed)
}
function deobfuscate(bigid)
{
constSeed = 1203793
return (bigid/constSeed)
}
I can even run the bigid through a base36 converter, to get a smaller alphanumeric id, publicly exposable.
Are issues with this approach? Any other suggestions?
If you don't want them with access to the ID's maybe use them only in $_SESSION variables or something along those lines.
If the data is visible to the end user, even if you hash or encrypt the data,
it will not be safe.
So here's my problem:
I have an article submission form with an optional image upload field.
When the user submits the form - this is roughly what happens:
if($this->view->form->isValid($_POST){
$db->beginTransaction();
try{
// save content of POST to Article table
if(!$this->_saveArticle($_POST)){
return;
}
// resize and save image using ID generated by previous condition
if(!$this->_saveImage($_FILES){
$db->rollback();
return;
}
// update record if image successfully generated
if(!$this->_updateArticle(){
$db->rollback();
}
$db->commit();
}
}catch (Exception $e){
$db->rollback()
}
All Models are saved using mappers, which automate "UPSERT" functionality by checking for the existence of a surrogate key
public function save($Model){
if(!is_null($Model->id_article){
$Mapper->insert($Model->getFields());
return;
}
$Mapper->update($Model->getFields(),$Model->getIdentity());
}
The article table has a composite UNIQUE index of ID,Title and URL. In addition, I'm generating a UID that gets added to the ID field of the Model prior to insert (instead of auto-incrementing)
When I try to execute this, it runs fine for the first article inserted into the table - but subsequent calls (with radically different input) triggers a DUPLICATE KEY error. MySQL throws back the ID generated in condition 1 (_saveArticle) and complains that the key already exists...
I've dumped out the Model fields (and the condition state - i.e. insert | update) and they proceed as expected (pseudo):
inserting!
id = null
title = something
content = something
image = null
updating!
id = 1234123412341234
title = something
content = something else
image = 1234123412341234.jpg
This row data is not present in the database.
I figure this could be one of a few things:
1: I'm loading a secondary DB adapter on user login, allowing them to interface with several sites from one login - this might be confusing the transaction somehow
2: It's a bug of some description in the Zend transaction implementation (possibly triggered by 1)
3: I need to replace the save() with an INSERT ... ON DUPLICATE
4: I should restructure the submission process, or generate a name for the image that isn't dependent on the UID of the previously inserted row.
Still hunting, but I was wondering if anyone else has encountered this kind of issue or could point me in the direction of a solution
best SWK
OK - just for the record, this is entirely possible. The problem was in my application architecture. I was catching Exceptions in my Mapper classes that were handling persistence - and then querying them to return boolean states and thus interrupt the process. This was in turn breaking the try/catch loop which was preventing the insert/update from working correctly.
To summarise - Yes - you CAN insert and update the same row in a single transaction. I've ticked community wiki to cancel rep out