typeorm / typeorm

ORM for TypeScript and JavaScript. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, SAP Hana, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.
http://typeorm.io
MIT License
34.28k stars 6.32k forks source link

Entity id sometimes not returned with Entity data #6273

Open dalevfenton opened 4 years ago

dalevfenton commented 4 years ago

Issue type:

[X] question [ ] bug report [ ] feature request [ ] documentation issue

Database system/driver:

[ ] cordova [X] mongodb [ ] mssql [ ] mysql / mariadb [ ] oracle [ ] postgres [ ] cockroachdb [ ] sqlite [ ] sqljs [ ] react-native [ ] expo

TypeORM version:

[ ] latest [ ] @next [X] 0.2.17 (or put your version here)

Steps to reproduce or a small repository showing the problem: We have an Entity which is defined like so:

export interface CustomerModel {
    id?: any;
    name: string;
    title: string;
}

@Entity()
export class Customer implements CustomerModel {
    @ObjectIdColumn()
    id: ObjectID;

    @Index()
    @Column()
    name: string;

    @Column()
    title: string;
}

and we have a query that runs like this:

const manager = getConnectionManager();
const mongo = manager.has(name) ? manager.get(name) : manager.create(config)

if(!mongo.isConnected) { await mongo.connect() };
const customers = mongo.getRepository(Customer);

const result = await customers.find({ where: { name: name } });
if (result.length > 1) throw new Error('Multiple customers match the query');
return _.first(result);

most of the time (>90%), we get the full customer record like we would expect i.e:

Customer {
    id: 5cdd73ef7106f16ceb2438a9,
    name: 'test',
    title: 'Test'
}

but every so often we get the record but the id is not included on the entity that is returned

Customer {
    name: 'test',
    title: 'Test'
}

this code is running in lambda behind an API Gateway at AWS and my best guess is that something about how we are re-using or creating the database connection is causing an issue

as far as I know we haven't seen this issue with any of the other entities we have and they are configured similarly

any ideas about how this happens and how to fix it would be appreciated

dalevfenton commented 4 years ago

had time to do some debugging on this, is appears that when things work (the 90% case) the entity is only run through DocumentToEntityTransformer's transform one time and it correctly maps _id to id

The failure case occurs when somehow the entity that has already been mapped is run through the transform a second time, with the already mapped version. (i.e. _id has already been transformed to id, and when the transform tries to set id with the value of _id it comes back undefined and so it doesn't appear when the entity is finally returned.

I haven't had a chance to try and figure out how or why the entity is getting run through transform a second time, but I'll do some more debugging and try to determine what's going on there.

dalevfenton commented 4 years ago

With further testing what is happening is that when everything works correctly there is only 1 call to MongoEntityManager's modified cursor.toArray.

When it fails I see multiple calls to the modified cursor.next and then finally a cursor.toArray after the entity has already been mapped over a couple times and the _id has been mapped to id and then on a second time id gets set to undefined.

logs show the following:

Looking through the EntityManager & MongoEntityManager code I can't figure out how the cursor.next calls are happening because in the cases where the call succeeds (90% of the time or so) we don't see that cursor.next code path getting used and I don't see where that method gets invoked

dmitrye commented 1 year ago

I am running into the same issue. I believe it actually has to do with the Transform method of EntityManager when calling .create(). It looks like this method can't properly handle DB fields that have ._id while the name is id and returns an undefined value for id:undefined which is then removed leaving nothing in the object.

const mongo = manager.has(name) ? manager.get(name) : manager.create(config)

In your case, I would guess that when this conditional statement goes into the manager.create(config) portion thats when you're not getting back the id field.

In my case I even used @PrimaryColumn({name: "_id"}) to attempt the mapping of the fields to no avail.

MongoEntityManager does not come with the .create() method and it therefore falls through to the underlying EntityManager base class to handle which calls the Transform method where this is not being properly handled.

That's as far as I got so far. I wrote a deepReplace function to replace ._id with id in the data returned before it's being passed to .create() to circumvent the issue. I came here to open a ticket but found that this one is most likely the same issue.

Noticing that this is a question ticket, I would like to get confirmation and then open a separate ticket as a bug or have this converted to a bug.