I have read a lot of postings and articles on this (async and Futures), and I am unsure what is the current situation. I have attempted a number of different variations.
I am attempting the process the following code in sequence :
import 'dart:async' as async;
import 'dart:io';
import 'dart:math';
import 'package:postgresql/postgresql.dart' as pg;
var uri = 'postgres://admin:admin#localhost:5432/testdb';
List lgNames = ['Peter', 'Robert', 'Mary', 'Marg', 'William',
'Vern', 'Dermot', 'Monty', 'Peggy', 'Sue', 'William'];
List lgUsed = [];
Random rand1 = new Random();
void main() {
pg.connect(uri).then((oDb) {
print ("Connected to database");
fClearTable(oDb).then((String sResult){
print(sResult);
for (int iPos = 0; iPos < 3; iPos++) {
fListTable(oDb, "Select before Insert number ${iPos+1}").then((String sResult) =>
print(sResult));
fInsertData(oDb, "Insert Number ${iPos+1}").then((String sResult) =>
print(sResult));
fListTable(oDb, "Select after Insert number ${iPos+1}").then((String sResult) =>
print(sResult));
}
});
});
}
async.Future<String> fClearTable(oDb) {
async.Completer oCompleter = new async.Completer();
oDb.execute("DELETE FROM test01").then((oResult){
oCompleter.complete("Table has been cleared");
});
return oCompleter.future;
}
async.Future<List> fListTable(oDb, sMsg) {
async.Completer oCompleter = new async.Completer();
oDb.query("SELECT * FROM test01").toList().then((lResult){
String sResult = "$sMsg = $lResult";
oCompleter.complete(sResult);
});
return oCompleter.future;
}
async.Future<String> fInsertData(oDb, sMsg) {
async.Completer oCompleter = new async.Completer();
oDb.execute("Start Transaction").then((oResult){
String sName;
for (bool tFound = true; tFound;) {
int iPos = rand1.nextInt(10);
sName = lgNames[iPos];
tFound = false; // init
for (iPos = 0; iPos < lgUsed.length && !tFound; iPos++){
tFound = (sName == lgUsed[iPos]);
}
}
lgUsed.add(sName);
String sSql = """INSERT INTO test01 (name)
VALUES ('$sName')""";
print("$sSql");
oDb.execute(sSql).then((oVal){
oDb.execute("COMMIT").then((oVal){
oCompleter.complete(sMsg);
});
});
});
return oCompleter.future;
}
The intention of the program is to loop three times and :
a) Select the Table
b) Insert a row into the table.
c) Select the Table
The output from the program clearly shows all three Inserts simultaneously at the end.
The output from the program is as follows :
Connected to database
Table has been cleared
Select before Insert number 1 = []
INSERT INTO test01 (name)
VALUES ('Vern')
Select after Insert number 1 = []
Select before Insert number 2 = []
INSERT INTO test01 (name)
VALUES ('Peter')
Select after Insert number 2 = []
Select before Insert number 3 = []
INSERT INTO test01 (name)
VALUES ('Robert')
Select after Insert number 3 = []
Insert Number 1
Insert Number 2
Insert Number 3
A Select in psql on termination of the program shows :
testdb=# select * from test01;
id | name
-----+--------
157 | Vern
158 | Peter
159 | Robert
(3 rows)
Is there a way to achieve what I want IE. For the Selects after the Inserts show the new values in the table?
Any relevant comments are welcome.
To start, here's a great article about how Futures work in Dart.
What you really need to know is that the code inside Futures run after all synchronous code has completed. Further, Futures by definition complete asynchronously, so it is not reasonable to expect one Future to complete before another unless they are chained.
That is to say:
main() {
print('first');
someFutureCall().then((_) => print('second?'));
someOtherFutureCall().then((_) => print('third?'));
print('last?');
}
This will print: 'first' and then 'last?' and then the two Futures can complete in any order. You cannot tell which will complete first. All you know is that the synchronous code happens first.
To have the correct order, do:
main() {
print('first');
someFutureCall()
.then((_) => print('second?'))
.then((_) => someOtherFutureCall())
.then((_) => print('third?'))
.then((_) => print('last?'));
}
So your sample uses Future in a way that the order is not preserved. What you want is this:
void main() {
pg.connect(uri).then((oDb) {
print ("Connected to database");
return fClearTable(oDb);
}).then((String sResult){
print(sResult);
return Future.forEach([0, 1, 2], (i) {
return fListTable(oDb, "Select before Insert number ${iPos+1}")
.then((String sResult) => print(sResult))
.then((_) => fInsertData(oDb, "Insert Number ${iPos+1}"))
.then((String sResult) => print(sResult))
.then((_) => fListTable(oDb, "Select after Insert number ${iPos+1}"))
.then((String sResult) => print(sResult));
});
});
}
See also the API documentation for Futures.
Related
total amateur here! I am working on a bingo app where i have a 4 x 4 grid. Each row and each column has a bool property which indicates whether it's a completed line or not. So..
bool row1 = false,
bool row2 = false,
bool row3 = false,
bool row4 = false,
bool column1 = false,
bool column2 = false,
bool column3 = false,
bool column4 = false
If the user completes a row the property changes to 'true' - I've worked that bit out.
Now i want a 'completedRows' property which provides the count of rows that = true (and same for columns)
Is it possible to create a function that does this?
Not convenient, but definitely possible:
int get completedRowCount =>
(row1 ? 1 : 0) + (row2 ? 1 : 0) + (row3 ? 1 : 0) + (row4 ? 1 : 0);
Since you are using individual variables, you have to access those variables independently.
You could do something like:
int get completedRowCount =>
[row1, row2, row3, row4].where((b) => b).length;
but that's not any more general than the code above, and more wasteful in memory use.
If instead of using individual variables, you used lists of booleans, then you can more easily generalize to, say, more rows or columns.
Say, you had
List<bool> row = List.filled(4, false);
List<bool> column = List.filled(4, false);
then instead of row1 = true; you write row[1] = true;,
but you can then use:
int get completedRowCount => row.where((b) => b).length;
to get the completed count. Or:
int get completedRowCount {
var count = 0;
for (var value in row) {
if (value) count++;
}
return count;
}
I'd probably wrap everything up in a class, that would allow you to abstract over the row and column count. Something like:
class BingoGrid {
final List<bool> completedRows;
final List<bool> completedColumns;
// Other properties.
BingoGrid(int columns, int rows)
: completedRows = List<bool>.filled(rows, false),
completedColumns = List<bool>.filled(columns, false);
// ...
static bool _isTrue(bool b) => b;
int get completedRowCount => completedRows.where(_isTrue).length;
int get completedColumnCount => completedColumns.where(_isTrue).length;
}
Is there an elegant way to squash these two statements into one, similar to a plain SQL INSERT INTO t1 SELECT 'something', t2.x FROM t2 WHERE ...?:
const commentIds: Array<Pick<SectionComment, 'id'>> =
await this.articleRepository
.createQueryBuilder('article')
.select('sectionComment.id', 'id')
.innerJoin('article.sectionComments', 'sectionComment')
.where(
'article.vaultId = :vaultId and article.valueId = :valueId and sectionComment.userId = :userId',
{
valueId,
vaultId,
userId,
}
)
.getRawMany();
if (commentIds.length) {
await this.userSectionReadRepository
.createQueryBuilder()
.insert()
.into(UserSectionRead)
.values(
commentIds.map((sectionComment) => ({
userId,
commentId: sectionComment.id,
}))
)
.orIgnore()
.execute();
}
The problem is the values() method in InsertQueryBuilder does not accept a subquery function (qb: this) => string like the where() method does.
I want to generate INSERT statement from JSON. The problem that I have problem with recursion (in section where I am getting nested elements). it's seems that I am getting SO.
import 'dart:convert';
main() {
Map jsonMap = jsonDecode(
'{"id":"1","name":"sample","Lots":[{"id":"1","title":"books","objs":[{"book":"name"}]}]}');
KVToTableInsert(String tablename, Map jsonMap) {
List<String> insertNoticeKeys = [];
List<String> insertNoticeValues = [];
jsonMap.forEach((key, value) {
if (value is List) // nested
{
KVToTableInsert(key, jsonMap); // if comment this line all work
} else {
insertNoticeKeys.add(key);
insertNoticeValues.add(value);
}
});
String sql = "INSERT INTO $tablename (" +
insertNoticeKeys.map((e) => '"$e"').join(', ') +
") VALUES (" +
insertNoticeValues.map((e) => "'$e'").join(', ') +
")";
print(sql);
}
KVToTableInsert("RootTable", jsonMap);
}
I expected to get 3 INSERT statment:
INSERT INTO RootTable ("id", "name") VALUES ('1', 'sample')
INSERT INTO Lots ("id", "title") VALUES ('1', 'books')
INSERT INTO Objs ("book") VALUES ('name')
But I am getting crush on this app.
There was a slight problem in your code, basically in this part:
if (value is List){
KVToTableInsert(key, jsonMap); // if comment this line all work
}
If you closely, take a look at your KVToTableInsert, you're passing arguments Map and not list. Which fails your recursion.
What you need to do is to pass the item of the array, into your KVToTableInsert, and this will work as expected
FINAL SOLUTION
// taking your jsonMap directly, and not decoding it
void main() {
Map jsonMap = {
"id":"1",
"name":"sample",
"Lots":[
{
"id": "1",
"title":"books",
"objs":[
{
"book":"name"
}
]
}
]
};
void KVToTableInsert(tablename, jsonMap){
List<String> insertNoticeKeys = [];
List<String> insertNoticeValues = [];
jsonMap.forEach((key,value){
if (value is List)
{
// Here is the magic, you need to iterate over your list, and pass MAP, not LIST
value.forEach((item){ KVToTableInsert(key, item);});
} else {
insertNoticeKeys.add(key);
insertNoticeValues.add(value);
}
});
String sql = "INSERT INTO $tablename (" +
insertNoticeKeys.map((e) => '"$e"').join(', ') +
") VALUES (" +
insertNoticeValues.map((e) => "'$e'").join(', ') +
")";
print(sql);
}
KVToTableInsert("Rootable", jsonMap);
}
OUTPUT
INSERT INTO objs ("book") VALUES ('name')
INSERT INTO Lots ("id", "title") VALUES ('1', 'books')
INSERT INTO Rootable ("id", "name") VALUES ('1', 'sample')
Also, look at the Dart Programming Language Naming Conventions, it is a good practise to use lowerCamelCase for your function/method name.
I have table tb_Orders (it empty), which have fields^
- order_id (int) (primary key)
- order_date nchar(30)
In my application, when client make order, requests the function:
private int GetNewOrderId()
{
int ord_id = 0;
if (db.tb_Orders.Max(x => x.order_id) != null)
{
int ord = db.tb_Orders.Max(x => x.order_id);
ord_id = ord + 1;
}
else
{
ord_id = 1;
};
return ord_id;
}
which get the new order id (+1 to max order in table).
Operator "if" must, when the table is still empty, get id = 1;
But the result - error (when I try to get id).
ERROR TEXT: "Error converting cast a value type "Int32", as materialize value is null."
Try casting your order_id to a nullable integer when making the Max call:
private int GetNewOrderId()
{
int nextOrderId = db.tb_Orders.Max(x => (int?)x.order_id) ?? 1;
return nextOrderId;
}
You will also notice that in my example there's only a single SQL query to the database whereas you were making 2: one in the if statement and another one inside.
It seems your order_id is Nullable<int>. Use the Value property to get it's value, and you can also perform the query before if statement and don't execute the query twice:
var max = db.tb_Orders.Max(x => x.order_id);
if(max != null)
{
int ord = max.Value;
ord_id = ord + 1;
}
var getAllProducts = _productService.GetAllProducts();
if (productstest.Count > 0)
{
model.idproduct.Add(new SelectListItem()
{
Value = "0",
Text = _localizationService.GetResource("Common.All")
});
foreach (var m in getAllProducts)
model.idproduct.Add(new SelectListItem()
{
Value = m.Id.ToString(),
**Text = m.Size.Distinct().ToString(),**
Selected = model.Pid == m.Id
});
}
public virtual IList<Product> GetAllProducts(bool showHidden = false)
{
var query = from p in _productRepository.Table
orderby p.Name
where (showHidden || p.Published) &&
!p.Deleted
select p;
var products = query.ToList();
return products;
}
The issue is even i tried to populate the select list with distinct size using: Text = m.Size.Distinct().ToString(), but it shows the duplicate for instance 100 products are of size 33 cm , the list will populate the dropdownlist in the view with 33cm occuring 100 times , I dont want to show 100 times , just want to show 1 time, Can any one assist me with this issue ?
Presumably you are only trying to show one product of each different size... if so initialising your getAllProducts variable like so will do the trick:
var getAllProducts = _productService.GetAllProducts().GroupBy(p => p.Size).Select(g => g.First());