After reading TypeORM documentation, it is not clear to me, if it is safe to use lazy relations inside transactions. The documentation about transactions says:
All operations MUST be executed using the provided transactional entity manager.
Does it apply for loading related entities as well? When using lazy relations, can I do the following?
await dataSource.transaction(async (transactionalEntityManager) => {
const parentEntity = await transactionalEntityManager.findOneBy(ParentEntity, {
id: 1,
});
const childEntity = await parentEntity.child; // It works, but is it safe?
})
If not, what is the proper way of loading relations inside transactions? Thank you.
Related
Good morning everyone.
I have a question regarding the following snippet.
#Transaction()
fun(#TransactionManager() tem: EntityManager) {
const bird = await tem.findOne(Bird, "b1")
const worms = await bird.worms
// ...
}
It seems that await bird.worms triggers a query that doesn't run inside the transaction.
Am I correct ?
Should it be safe to assume that it uses the same transaction that was used to findOne.
Or does it use a standalone manager?
Thanks
Typeorm's official document states that if you use Lazy, you must use promise. If not promise, will default fetch type be eager loading? However, I checked and it seems to be loading Lazy, not Eager.
The default pitch type of JPA is as follows:
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
Is TypeOrm's default fetch type the same?
TLDR: It is neither lazy nor eager.
If you check Typeorm relation options code:
export interface RelationOptions {
...
/**
* Set this relation to be lazy. Note: lazy relations are promises. When you call them they return promise
* which resolve relation result then. If your property's type is Promise then this relation is set to lazy automatically.
*/
lazy?: boolean;
/**
* Set this relation to be eager.
* Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods.
* Only using QueryBuilder prevents loading eager relations.
* Eager flag cannot be set from both sides of relation - you can eager load only one side of the relationship.
*/
eager?: boolean;
...
}
You can see that Typeorm does not load the relation for any kind of relationship by default. Basically, it is neither lazy nor eager.
If you set neither lazy nor eager, it will not load the relationship at all unless you specified it in your find options, or in QueryBuilder.
See the below example from Typeorm Documentation for find:
const user = userRepository.find({
where: {
name: "John",
},
relations: ["project"],
});
// Think user has a one-to-many relationship with projects, then:
// const projects = user.projects;
If you specify lazy or eager then you don't need to specify it in find options. But you will still have to specify the join condition when using QueryBuilder.
For lazy:
const user = userRepository.find({
where: {
name: "John",
}
});
// Need await for `lazy`:
// const projects = await user.projects;
For eager:
const user = userRepository.find({
where: {
name: "John",
}
});
// No await for `eager`:
// const projects = user.projects;
Hope this helps. Cheers 🍻 !!!
I have a complex graph of objects that I am saving to the database. These objects can each connect to other graphs of a similar type. I wish to save the graph of objects and all of the graphs they connect to recursively.
Is it bad practice to aggregate all of the saves and execute them under one transaction? Should each graph be saved seperately?
Currently I save each subgraph separately. Below is example code (.NET)
public async Task<GraphObj> SaveAllGraphs(GraphObj graph)
{
foreach (var node in graph.nodes)
{
if (node.subGraph != null)
{
await SaveGraph(node.subGraph);
}
}
return await SaveGraphInstance(graph);
}
async public Task SaveGraphInstance(GraphObj graph)
{
var txClient = (ITransactionalGraphClient)_client;
await txClient.ConnectAsync();
using (var tx = txClient.BeginTransaction())
{
await _client.Cypher.Create(...).ExecuteWithoutResultsAsync();
await _client.Cypher.Create(...).ExecuteWithoutResultsAsync();
await tx.CommitAsync();
}
}
}
There's no real way to answer as it's dependent on your use case.
If your subgraphs etc should all be there, or not at all, then you should do it all in one transaction. This is no different to anything else - database or indeed application wise. The benefit of the transaction is that is any one part of it fails, it all fails.
In terms of performance, there's no real difference between the two approaches, in both you are making n calls to the DB.
I'm a newbie in node.js(firebase functions) and Dialogflow fulfillment, I want to retrieve data in a different directory. first is to check the nearest store, and then check the inventory of the store in a different directory, but I have a problem with return. so how I can fix it?
app.intent('location_checking - yes',(conv)=> {
var store= database.ref('store');
var inventory = database.ref('inventory);
var keystore=[];
return store.orderByKey().on("child_added", function(snapshot){
keystore.push(snapshot.key)
})
return inventory.child(keystore).on("value", function(snapshot){
var tomato =snapshot.val().tomato;
//and then check the nearest store with available stock
})
})
You have a few issues, some of them conceptual.
The first is that you're using on() and the "child_added" event. But since this is happening inside an Intent Handler, which only gets triggered when the user has done something, you can't also listen to conventional events and react to them. Instead, you should probably use once() with a "value" event - so you query for the values you want and work with them.
Second is that the Intent Handler expects you to return a Promise if you are doing any asynchronous operations - anything that would require a callback handler, for example. This would require some restructuring in how you make the calls to once(), so they return a Promise, and you take action in a .then() block. Since you can chain Promises together using .then(), you can make multiple calls this way.
I'm not sure that ordering by key will get you the "closest" store, but I'll ignore that for the moment to illustrate the rest of the code.
So that part of your code might look something like
return store.orderByKey().once("value")
.then( snapshot => {
// Handle the results
// ...
// Make the next query
return inventory.child( childKey ).once("value");
})
.then( snapshot => {
// Handle the inventory results
});
You can also do this with async/await by making your Intent Handler an async function and then calling await on the database calls. Possibly something like.
app.intent('location_checking - yes', async (conv) => {
const store= database.ref('store');
const inventory = database.ref('inventory);
//...
const storeSnapshot = await store.orderByKey().once("value");
// Do something with the store snapshot
const inventorySnapshot = await inventory.child( childKey ).once("value");
// Do stuff with the inventory snapshot
})
This is in a WPF application.
Currently I have the following code which works, but is slow:
public void IgnoreOffers(ICollection<Offer> offers)
{
using (var context = new DbContext())
{
foreach (Offer o in offers)
{
var offer = context.Offers.Find(o.Id);
offer.Ignore = true;
}
context.SaveChanges();
}
}
I know all the offers exist already in the database
Is there a more performant way of doing this?
Disclaimer: I'm the owner of the project Entity Framework Plus
This library allows you to perform BatchUpdate
context.Offers.Where(o => offers.Contains(o.Id)
.BatchUpdate(o => new Offer() { Ignore = true });
If you have too many offers, you might need to do it in multiple batch as the number of parameter in SQL is limited.
Everything will be done on the database side, so no offer will be needed to be loaded on the application side.
This method will offer you the best performance.
Disclaimer: I'm the owner of the project Entity Framework Extensions
This library is not free but offers the BulkUpdate features.
// BulkUpdate
context.BulkUpdate(offers);
// If you want to only update the `Ignore` property
context.BulkUpdate(offers, o => o.ColumnInputExpression = x => new { x.Ignore });