Test funzione di salvataggio sul DB e refactoring del codice

Test degli spunti ottenuti da una chat con Gemini.
- [X] Test della classe cRac.cs
- [X] Test della funzione di salvataggio sul DB
- [X] Test della funzione di lettura dal DB
- [ ] Test della funzione di ricerca nel DB
- [ ] Test funzione di cancellazione nel DB
- [ ] Test nella ricerca di duplicati
- [X] Test della funzione di esportazione in html con stile
- [ ] Inserire verifica su racconto duplicato, prima di salvare un nuovo racconto.

Riepilogo della chat:
- connesso a gemini con chrome e utente mirko
- dato in pasto il file *ATDB Database Schema.sql* con la struttura del DB
- richiesto la classe cRac per rappresentare un racconto.
- ottenuto *cRac.cs*
- richiesto come salvare un cRac sul DB con diagnostica errori ed evitando spurie sul DB
- ottenuto *Salvataggio cRac su DB.cs*
- richiesto come leggere un racconto
- ottenuto *GetRaccontoById.cs*
- richiesto: ragruppiamo le funzioni di helper su un file Helper.cs e quelle di lettura/scrittura sul DB sul file DB.cs
- ottenuto *Helper.cs* e *DB.cs*
- richiesto: facciamo le ricerche avanzate per titolo, tag, autore, data di creazione, sorgente
- ottenuta la nuova versione di *Aggiornamento 1 di DB.cs*
- richiesto come eliminare un racconto dal DB
- ottenuta la nuova versione di *Aggiornamento 2 di DB.cs*
- richiesto: come ricercare racconti duplicati
- ottenuto spunti di ricerca salvati in *Ricerca duplicati.cs*
- richiesto come fare il merge di racconti duplicati
- ottenuto *merge.cs*
- richiesto delle idee per la UI
- ottenuto il file *UI_idee.md*
- richiesto un esempio di esportazione di un racconto in html con stile
- ottenuto: *export.cs*
This commit is contained in:
CSiCoAlm committed 2025-12-28 17:10:00 +01:00
1 parent 56920d059d
commit 0db12c844f
4 files changed
+357 -145

No files matched your search

+1
View File
@@ -62,6 +62,7 @@
<Compile Include="Data\cNota.cs" />
<Compile Include="Data\cRac.cs" />
<Compile Include="Data\DBTools.cs" />
<Compile Include="Data\Helper.cs" />
<Compile Include="Net\eLinkState.cs" />
<Compile Include="Net\cLink_A69.cs" />
<Compile Include="Service\Service_Crawler.cs" />
+207 -110
View File
@@ -1071,106 +1071,6 @@ namespace A69WD {
}
}
public cRac GetRacById(long IDrac) {
/*
* Il metodo più pulito e leggibile consiste nell'eseguire una query principale per
* i dati del racconto e poi delle query mirate per popolare le collezioni.
*
* Ecco come implementare la funzione in C# utilizzando Microsoft.Data.Sqlite.
*
* Spiegazione della logica:
* - LEFT JOIN: Ho usato i LEFT JOIN nella query principale così, anche se un racconto
* non ha ancora una trama o un voto assegnato, il record viene comunque recuperato
* correttamente senza errori.
* - Mapping Manuale: Poiché non stiamo usando un ORM pesante (come Entity Framework),
* mappiamo manualmente le colonne del reader alle proprietà dell'oggetto cRac.
* - Gestione Valori Nulli: Noterai il controllo reader["Trama"]?.ToString() ?? "" che
* evita eccezioni nel caso in cui certi campi opzionali siano vuoti nel database.
* - Helper GetList: Per evitare di riscrivere lo stesso codice 3 volte per Autori,
* Tags e Origini, ho creato una piccola funzione di supporto.
*
* Questo approccio è molto performante su SQLite e ti restituisce l'oggetto cRac
* esattamente come lo avevi immaginato, pronto per essere mostrato nella tua
* interfaccia utente.
*
*/
lock (objLock) {
cRac rac = null;
// 1. Query principale: Dati Racconto, Trama e Voto
string queryBase = @"
SELECT r.*, t.Trama, v.Voto as Voto
FROM Racconti r
LEFT JOIN Trame t ON r.IDrac = t.IDrac
LEFT JOIN Links_Rac_Vot lrv ON r.IDrac = lrv.IDrac
LEFT JOIN Voti v ON lrv.IDvot = v.IDvot
WHERE r.IDrac = @IDrac";
using (SQLiteCommand cmd = new SQLiteCommand(queryBase, cn)) {
cmd.Parameters.AddWithValue("@IDrac", IDrac);
using (SQLiteDataReader reader = cmd.ExecuteReader()) {
if (reader.Read()) {
rac = new cRac {
IDrac = Convert.ToInt64(reader["IDrac"]),
Title = reader["Title"].ToString(),
SubTitle = reader["SubTitle"].ToString(),
Creato = DateTime.Parse(reader["Creato"] as string),
Updated = DateTime.Parse(reader["Aggiornato"].ToString()),
Trama = reader["Trama"]?.ToString() ?? "",
Voto = reader["Voto"]?.ToString() ?? "Non Letto"
};
}
}
}
if (rac == null) return null; // Racconto non trovato
// 2. Recupero Autori
rac.Autori = GetList(cn, "SELECT a.Autore FROM Autori a JOIN Links_Rac_Aut l ON a.IDaut = l.IDaut WHERE l.IDrac = @IDrac", IDrac);
// 3. Recupero Tags
rac.Tags = GetList(cn, "SELECT t.Tag FROM Tags t JOIN Links_Rac_Tag l ON t.IDtag = l.IDtag WHERE l.IDrac = @IDrac", IDrac);
// 4. Recupero Origini
rac.Origini = GetList(cn, "SELECT o.Origine FROM Origini o JOIN Links_Rac_Ori l ON o.IDori = l.IDori WHERE l.IDrac = @IDrac", IDrac);
// 5. Recupero Note (Key-Value)
string queryNotes = @"
SELECT n.NKey, n.NVal
FROM Notes n
JOIN Links_Rac_Not l ON n.IDnot = l.IDnot
WHERE l.IDrac = @IDrac";
using (var cmd = new SQLiteCommand(queryNotes, cn)) {
cmd.Parameters.AddWithValue("@IDrac", IDrac);
using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) {
cNota nt = new cNota(reader["NKey"].ToString(), reader["NVal"].ToString());
rac.Notes.Add(nt);
}
}
}
// // 6. Recupero Segnalini
// string querySng = @"
// SELECT s.Linea, s.Segnalino
// FROM Segnalini s
// JOIN Links_Rac_Sng l ON s.IDsng = l.IDsng
// WHERE l.IDrac = @IDrac";
// using (var cmd = new SQLteCommand(querySng, cn) {
// cmd.Parameters.AddWithValue("@IDrac", IDrac);
// using (var reader = cmd.ExecuteReader()) {
// while (reader.Read()) {
// rac.Segnalini.Add(new SegnalinoInfo {
// Linea = Convert.ToInt32(reader["Linea"]),
// Descrizione = reader["Segnalino"].ToString()
// });
// }
// }
// }
return rac;
}//lock
}
/// <summary>
/// Importa il racconto passato sul DB
/// </summary>
@@ -1825,21 +1725,218 @@ namespace A69WD {
// Helper per la lettura rapida di liste di stringhe
public static List<string> GetList(SQLiteConnection conn, string sql, long IDrac) {
var list = new List<string>();
using (var cmd = new SQLiteCommand(sql, conn)) {
cmd.Parameters.AddWithValue("@IDrac", IDrac);
using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) list.Add(reader.GetValue(0).ToString());
// Metodi ricavati ottimizzando i miei tramite gemini
/// <summary>
/// Ricava il racconto con l'ID indicato leggendolo dal DB
/// </summary>
/// <param name="IDrac">Indice identificativo del racconto voluto</param>
/// <returns>oggetto cRac che rappresenta il racconto</returns>
public cRac GetRacById(long IDrac) {
/*
* Il metodo più pulito e leggibile consiste nell'eseguire una query principale per
* i dati del racconto e poi delle query mirate per popolare le collezioni.
*
* Ecco come implementare la funzione in C# utilizzando Microsoft.Data.Sqlite.
*
* Spiegazione della logica:
* - LEFT JOIN: Ho usato i LEFT JOIN nella query principale così, anche se un racconto
* non ha ancora una trama o un voto assegnato, il record viene comunque recuperato
* correttamente senza errori.
* - Mapping Manuale: Poiché non stiamo usando un ORM pesante (come Entity Framework),
* mappiamo manualmente le colonne del reader alle proprietà dell'oggetto cRac.
* - Gestione Valori Nulli: Noterai il controllo reader["Trama"]?.ToString() ?? "" che
* evita eccezioni nel caso in cui certi campi opzionali siano vuoti nel database.
* - Helper GetList: Per evitare di riscrivere lo stesso codice 3 volte per Autori,
* Tags e Origini, ho creato una piccola funzione di supporto.
*
* Questo approccio è molto performante su SQLite e ti restituisce l'oggetto cRac
* esattamente come lo avevi immaginato, pronto per essere mostrato nella tua
* interfaccia utente.
*
*/
lock (objLock) {
try {
cRac rac = null;
// 1. Query principale: Dati Racconto, Trama e Voto
string queryBase = @"
SELECT r.*, t.Trama, v.Voto as Voto
FROM Racconti r
LEFT JOIN Trame t ON r.IDrac = t.IDrac
LEFT JOIN Links_Rac_Vot lrv ON r.IDrac = lrv.IDrac
LEFT JOIN Voti v ON lrv.IDvot = v.IDvot
WHERE r.IDrac = @IDrac";
using (SQLiteCommand cmd = new SQLiteCommand(queryBase, cn)) {
cmd.Parameters.AddWithValue("@IDrac", IDrac);
using (SQLiteDataReader reader = cmd.ExecuteReader()) {
if (reader.Read()) {
rac = new cRac {
IDrac = Convert.ToInt64(reader["IDrac"]),
Title = reader["Title"].ToString(),
SubTitle = reader["SubTitle"].ToString(),
Creato = DateTime.Parse(reader["Creato"] as string),
Updated = DateTime.Parse(reader["Aggiornato"].ToString()),
Trama = reader["Trama"]?.ToString() ?? "",
Voto = reader["Voto"]?.ToString() ?? "Non Letto"
};
}
}
}
if (rac == null) return null; // Racconto non trovato
// 2. Recupero Autori
rac.Autori = Helper.GetList(cn, "SELECT a.Autore FROM Autori a JOIN Links_Rac_Aut l ON a.IDaut = l.IDaut WHERE l.IDrac = @IDrac", IDrac);
// 3. Recupero Tags
rac.Tags = Helper.GetList(cn, "SELECT t.Tag FROM Tags t JOIN Links_Rac_Tag l ON t.IDtag = l.IDtag WHERE l.IDrac = @IDrac", IDrac);
// 4. Recupero Origini
rac.Origini = Helper.GetList(cn, "SELECT o.Origine FROM Origini o JOIN Links_Rac_Ori l ON o.IDori = l.IDori WHERE l.IDrac = @IDrac", IDrac);
// 5. Recupero Note (Key-Value)
string queryNotes = @"
SELECT n.NKey, n.NVal
FROM Notes n
JOIN Links_Rac_Not l ON n.IDnot = l.IDnot
WHERE l.IDrac = @IDrac";
using (var cmd = new SQLiteCommand(queryNotes, cn)) {
cmd.Parameters.AddWithValue("@IDrac", IDrac);
using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) {
cNota nt = new cNota(reader["NKey"].ToString(), reader["NVal"].ToString());
rac.Notes.Add(nt);
}
}
}
// // 6. Recupero Segnalini
// string querySng = @"
// SELECT s.Linea, s.Segnalino
// FROM Segnalini s
// JOIN Links_Rac_Sng l ON s.IDsng = l.IDsng
// WHERE l.IDrac = @IDrac";
// using (var cmd = new SQLteCommand(querySng, cn) {
// cmd.Parameters.AddWithValue("@IDrac", IDrac);
// using (var reader = cmd.ExecuteReader()) {
// while (reader.Read()) {
// rac.Segnalini.Add(new SegnalinoInfo {
// Linea = Convert.ToInt32(reader["Linea"]),
// Descrizione = reader["Segnalino"].ToString()
// });
// }
// }
// }
return rac;
} catch (Exception ex) {
Logger.Error($"GetRacById({IDrac}) error: {ex.Message}");
return null;
}
}
return list;
}//lock
}
/// <summary>
/// Salva il racconto nel DB
/// </summary>
/// <param name="racconto">Racconto da salvare sul DB</param>
/// <returns>Esito del salvataggio</returns>
public SaveResult SalvaRacconto(cRac racconto) {
lock (objLock) {
using (var transaction = cn.BeginTransaction()) {
try {
// 1. Inserimento Record Principale [Racconti]
var cmdRac = new SQLiteCommand(@"
INSERT INTO Racconti (Title, SubTitle, Creato, Aggiornato)
VALUES (@t, @st, @c, @a);
SELECT last_insert_rowid();",
cn, transaction);
cmdRac.Parameters.AddWithValue("@t", racconto.Title);
cmdRac.Parameters.AddWithValue("@st", racconto.SubTitle);
cmdRac.Parameters.AddWithValue("@c", racconto.Creato);
cmdRac.Parameters.AddWithValue("@a", racconto.Updated);
long newIDrac = (long)cmdRac.ExecuteScalar(); //
racconto.IDrac = newIDrac;
// 2. Salvataggio Relazioni Molti-a-Molti
// Inserimento Trama (Tabella [Trame])
if (!string.IsNullOrWhiteSpace(racconto.Trama)) {
var cmdTra = new SQLiteCommand("INSERT INTO Trame (IDrac, Trama) VALUES (@id, @txt)", cn, transaction);
cmdTra.Parameters.AddWithValue("@id", newIDrac);
cmdTra.Parameters.AddWithValue("@txt", racconto.Trama);
cmdTra.ExecuteNonQuery(); //
}
// Inserimento Voto (Tabella [Links_Rac_Vot])
if (!string.IsNullOrWhiteSpace(racconto.Voto)) {
var cmdVot = new SQLiteCommand("INSERT OR REPLACE INTO Links_Rac_Vot (IDrac, IDvot) VALUES (@r, @v)", cn, transaction);
cmdVot.Parameters.AddWithValue("@r", newIDrac);
cmdVot.Parameters.AddWithValue("@v", racconto.Voto); // IDvot corrisponde al valore rating
cmdVot.ExecuteNonQuery();
}
// 3. Salvataggio Relazioni Molti-a-Molti
// Autori
racconto.Autori.ForEach(autore => {
long idAut = Helper.GetOrCreateId(cn, transaction, "Autori", "Autore", "IDaut", autore);
Helper.ExecuteLink(cn, transaction, "Links_Rac_Aut", "IDaut", newIDrac, idAut);
});
// Tags
racconto.Tags.ForEach(tag => {
long idTag = Helper.GetOrCreateId(cn, transaction, "Tags", "Tag", "IDtag", tag);
Helper.ExecuteLink(cn, transaction, "Links_Rac_Tag", "IDtag", newIDrac, idTag);
});
// Origini Multiple
racconto.Origini.ForEach(origine => {
long idOri = Helper.GetOrCreateId(cn, transaction, "Origini", "Origine", "IDori", origine);
Helper.ExecuteLink(cn, transaction, "Links_Rac_Ori", "IDori", newIDrac, idOri);
});
// Note (Coppia Chiave-Valore)
foreach (var nota in racconto.Notes) {
var idNot = Helper.GetOrCreateNoteId(cn, transaction, nota.Key, nota.Description);
Helper.ExecuteLink(cn, transaction, "Links_Rac_Not", "IDnot", newIDrac, idNot);
}
// // 4. Inserimento Segnalini (Tabella [Segnalini])
// foreach (var sng in racconto.Segnalini) {
// var cmdSng = new SqliteCommand(@"
// INSERT INTO Segnalini (Linea, Segnalino) VALUES (@l, @s);
// SELECT last_insert_rowid();", connection, transaction);
// cmdSng.Parameters.AddWithValue("@l", sng.Linea);
// cmdSng.Parameters.AddWithValue("@s", sng.Descrizione);
// long idSng = (long)cmdSng.ExecuteScalar();
//
// // Link Segnalino al Racconto
// ExecuteLink(connection, transaction, "Links_Rac_Sng", "IDsng", newIDrac, idSng);
// }
transaction.Commit();
Logger.Information($"Inserito racconto {newIDrac}");
return SaveResult.Success;
} catch (Exception ex) {
transaction.Rollback();
Logger.Information($"Errore SQL durante l'inserimento del racconto racconto {racconto.Title}");
return SaveResult.GenericError;
}
}//transaction
}//lock
}
} // class DBTools
} //class DBTools
}//namespace A69WD
+88
View File
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SQLite;
namespace A69WD.Data {
/*
*
* Questo file contiene i metodi statici per gestire le tabelle di lookup e i collegamenti.
* Nota che questi metodi accettano SqliteTransaction per operare all'interno del contesto della transazione principale.
*
* Vantaggi di questa suddivisione:
* - Helper statico: Non serve istanziare nulla per usare le funzioni di utilità.
* - Manutenibilità: Se cambi il modo in cui cerchi un autore, lo cambi in un solo punto (Helper.cs).
* - Transazioni pulite: DBTools.cs si occupa solo di aprire la transazione e chiamare in ordine i "mattoncini" forniti dall'Helper.
*
*/
public static class Helper {
// Ottiene ID o crea nuovo record per Autori, Tags, Origini
public static long GetOrCreateId(SQLiteConnection conn, SQLiteTransaction trans, string table, string colName, string idCol, string value) {
var cmdSelect = new SQLiteCommand($"SELECT {idCol} FROM {table} WHERE {colName} = @v", conn, trans);
cmdSelect.Parameters.AddWithValue("@v", value);
var result = cmdSelect.ExecuteScalar();
if (result != null) return (long)result;
var cmdInsert = new SQLiteCommand($"INSERT INTO {table} ({colName}) VALUES (@v); SELECT last_insert_rowid();", conn, trans);
cmdInsert.Parameters.AddWithValue("@v", value);
return (long)cmdInsert.ExecuteScalar();
}
// Helper specifico per le Note (Key-Value)
public static long GetOrCreateNoteId(SQLiteConnection conn, SQLiteTransaction trans, string key, string val) {
var cmdSelect = new SQLiteCommand("SELECT IDnot FROM Notes WHERE NKey = @k AND NVal = @v", conn, trans);
cmdSelect.Parameters.AddWithValue("@k", key);
cmdSelect.Parameters.AddWithValue("@v", val);
var result = cmdSelect.ExecuteScalar();
if (result != null) return (long)result;
var cmdInsert = new SQLiteCommand("INSERT INTO Notes (NKey, NVal) VALUES (@k, @v); SELECT last_insert_rowid();", conn, trans);
cmdInsert.Parameters.AddWithValue("@k", key);
cmdInsert.Parameters.AddWithValue("@v", val);
return (long)cmdInsert.ExecuteScalar();
}
// Crea il legame nelle tabelle Links_...
public static void ExecuteLink(SQLiteConnection conn, SQLiteTransaction trans, string table, string targetIdCol, long idRac, long targetId) {
var cmd = new SQLiteCommand($"INSERT OR IGNORE INTO {table} (IDrac, {targetIdCol}) VALUES (@r, @t)", conn, trans);
cmd.Parameters.AddWithValue("@r", idRac);
cmd.Parameters.AddWithValue("@t", targetId);
cmd.ExecuteNonQuery();
}
// Helper per la lettura rapida di liste di stringhe
public static List<string> GetList(SQLiteConnection conn, string sql, long IDrac) {
var list = new List<string>();
using (var cmd = new SQLiteCommand(sql, conn)) {
cmd.Parameters.AddWithValue("@IDrac", IDrac);
using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) list.Add(reader.GetValue(0).ToString());
}
}
return list;
}
}//class Helper
public enum SaveResult {
Success = 0,
DatabaseConnectionError = 1,
MainRecordInsertError = 2,
TramaInsertError = 3,
RelationInsertError = 4,
SegnaliniInsertError = 5,
GenericError = 99
}
}//namespace A69WD.Data
+61 -35
View File
@@ -183,45 +183,71 @@ namespace A69WD {
}
private Service_Importer serviceImporter = null;
private void btnTest_Click(object sender, EventArgs e) {
// Test dell'esportazione in html
//int MaxRac = 60;
//pbST.Maximum = MaxRac;
//pbST.Minimum = 0;
//pbST.Value = 0;
//lblST.Text = "Inizio esportazione racconti";
//for (int i = 0; i < 61; i++) {
// cRac rac = App.DB.GetRacById(i);
// cRac.EsportaInHtml(rac, Cfg.Settings.PathDownloadWorking);
// pbST.Value = i;
// lblST.Text = $"Esportato racconto ID={rac.strIDrac}";
//}
//lblST.Text = $"Fine esportazione {MaxRac.ToString("000")} racconti";
private async void btnTest_Click(object sender, EventArgs e) {
// // Test dell'esportazione in html
// int MaxRac = 60;
// pbST.Maximum = MaxRac;
// pbST.Minimum = 0;
// pbST.Value = 0;
// lblST.Text = "Inizio esportazione racconti";
// for (int i = 0; i < 61; i++) {
// cRac rac = App.DB.GetRacById(i);
// cRac.EsportaInHtml(rac, Cfg.Settings.PathDownloadWorking);
// pbST.Value = i;
// lblST.Text = $"Esportato racconto ID={rac.strIDrac}";
// }
// lblST.Text = $"Fine esportazione {MaxRac.ToString("000")} racconti";
// Test funzione di lettura dal DB
Logger.Information("-- Start test 100*50 letture originali --");
DateTime start = DateTime.Now;
for (int j = 0; j < 100; j++) {
for (int i = 1; i <= 50; i++) {
cRac rac = App.DB.GetRacById_Orig(i);
if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}");
}
}
TimeSpan tp = start-DateTime.Now;
Logger.Information($" End test in {tp.ToString()}");
Logger.Information("-- Start test 100*50 letture nuove --");
start = DateTime.Now;
for (int j = 0; j < 100; j++) {
for (int i = 1; i <= 50; i++) {
cRac rac = App.DB.GetRacById(i);
if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}");
}
}
tp = start - DateTime.Now;
Logger.Information($" End test in {tp.ToString()}");
// // Test funzione di lettura dal DB
// Logger.Information("-- Start test 100*50 letture originali --");
// DateTime start = DateTime.Now;
// for (int j = 0; j < 100; j++) {
// for (int i = 1; i <= 50; i++) {
// cRac rac = App.DB.GetRacById_Orig(i);
// if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}");
// }
// }
// TimeSpan tp = start-DateTime.Now;
// Logger.Information($" End test in {tp.ToString()}");
// Logger.Information("-- Start test 100*50 letture nuove --");
// start = DateTime.Now;
// for (int j = 0; j < 100; j++) {
// for (int i = 1; i <= 50; i++) {
// cRac rac = App.DB.GetRacById(i);
// if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}");
// }
// }
// tp = start - DateTime.Now;
// Logger.Information($" End test in {tp.ToString()}");
// Test di salvataggio sul DB
// leggo 3 nuovi racconti
string file = @"C:\Commesse\_zPrivate\A69WD\Test\Down\A69_altro_Decisione-estrema_133364.html";
cLink_A69 lk = new cLink_A69(file, "A69_altro_Decisione-estrema_133364.html");
cRac rac1 = await lk.EstraiRac(file);
file = @"C:\Commesse\_zPrivate\A69WD\Test\Down\A69_altro_Decisione_133364 - Copy.html";
lk = new cLink_A69(file, "A69_altro_Decisione_133364 - Copy.html");
cRac rac2 = await lk.EstraiRac(file);
file = @"C:\Commesse\_zPrivate\A69WD\Test\Down\A69_altro_Dammi-un-bacio_153877.html";
lk = new cLink_A69(file, "A69_altro_Dammi-un-bacio_153877.html");
cRac rac3 = await lk.EstraiRac(file);
file = @"C:\Commesse\_zPrivate\A69WD\Test\Down\A69_altro_dammi-la-mano_157904.html";
lk = new cLink_A69(file, "A69_altro_dammi-la-mano_157904.html");
cRac rac4 = await lk.EstraiRac(file);
// // Controprova:
// var res1 = App.DB.ImportRac(rac1);
// var res2 = App.DB.ImportRac(rac2);
// var res3 = App.DB.ImportRac(rac3);
// var res4 = App.DB.ImportRac(rac4);
// Provo a salvarli
var res1 = App.DB.SalvaRacconto(rac1);
var res2 = App.DB.SalvaRacconto(rac2);
var res3 = App.DB.SalvaRacconto(rac3);
var res4 = App.DB.SalvaRacconto(rac4);
//
}