My table will contain many repeating strings like domains. For minimize database size I want save only unique domains in other table and use domains id in main table.
All times I did it manually, but a short time ago I found out SQLite can did it automatically.
Now I try use Many-To-One relationship with "FOREIGN" key, but without success. Maybe I do something wrong.
Example code
Tables classes:
public class Domains
{
public Domains() { }
public Domains(string domain) { this.Domain = domain; }
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[Unique, MaxLength(64)]
public string Domain { get; set; }
}
public class Statistics
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Timestamp { get; set; }
[ForeignKey(typeof(Domains))]
public int DomainId { get; set; }
public int Status { get; set; }
[ManyToOne(CascadeOperations = CascadeOperation.All)]
public Domains Domain { get; set; }
}
Main code:
static void Main(string[] args)
{
var dbFile = "stats.db";
var domains = new[] { "stackoverflow.com", "superuser.com", "serverfault.com", "google.com", "microsoft.com" };
var statList = new List<Statistics>();
var sqlBase = new SQLiteConnection(dbFile);
sqlBase.Execute("PRAGMA foreign_keys = ON");
sqlBase.CreateTable<Domains>();
sqlBase.CreateTable<Statistics>();
Console.WriteLine(SQLite3.LibVersionNumber());
var runTimestamp = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
foreach (var domain in domains)
{
HttpWebResponse resp = null;
var status = -1;
try
{
resp = (HttpWebResponse)WebRequest.Create("http://" + domain).GetResponse();
}
catch { };
status = (int)resp.StatusCode;
var stat = new Statistics();
stat.Domain = new Domains(domain);
stat.Status = status;
stat.Timestamp = runTimestamp;
statList.Add(stat);
}
sqlBase.InsertOrIgnoreAllWithChildren(statList); // Modification "INSERT" with "OR IGNORE"
Console.WriteLine(@"Table ""Domains""");
foreach (var table in sqlBase.Table<Domains>())
{
Console.WriteLine("Id: {0}\tDomain: {1}", table.Id, table.Domain);
}
Console.WriteLine();
Console.WriteLine(@"Table ""Statistics""");
foreach (var table in sqlBase.Table<Statistics>())
{
Console.WriteLine("Id: {0}\tDomain Id: {1}", table.Id, table.DomainId);
}
Console.WriteLine();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
After first run it's look fine.
But after second run, when domains repeated - sqlite extensions insert wrong domains id
Where I make mistake?
In your code you're creating new Domains
entities every time you try to save new stats.
SQLite-Net Extensions needs the primary key of the referenced object in order to assign the foreign key. It seems that your InsertOrIgnoreAllWithChildren
is assigning 10
to all your Domains
objects even when they're not being inserted.
What you need to do is fetch your current domains in order to get the correct primary key.
Try something like this:
var dbFile = "stats.db";
var statList = new List<Statistics>();
var sqlBase = new SQLiteConnection(dbFile);
sqlBase.Execute("PRAGMA foreign_keys = ON");
sqlBase.CreateTable<Domains>();
sqlBase.CreateTable<Statistics>();
// Fetch existing domains from database
var domains = sqlBase.Table<Domains>().toList();
if (domains.isEmpty()) {
// Insert domains into database if they don't exist
var domainNames = new[] { "stackoverflow.com", "superuser.com", "serverfault.com", "google.com", "microsoft.com" };
domains = domainNames.Select(domainName => new Domain(domainName));
sqlBase.InsertAll(domains);
}
var runTimestamp = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
foreach (var domain in domains)
{
HttpWebResponse resp = null;
var status = -1;
try
{
resp = (HttpWebResponse)WebRequest.Create("http://" + domain.domain).GetResponse();
}
catch { };
status = (int)resp.StatusCode;
var stat = new Statistics();
stat.Domain = domain; // Assign the existing domain object
stat.Status = status;
stat.Timestamp = runTimestamp;
statList.Add(stat);
}
// Insert only Statistics (Domains already exist), and assign foreign keys
sqlBase.InsertAllWithChildren(statList);
You can use Domain
as primary (and foreign) key and your current code will work as expected:
public class Domains
{
public Domains() { }
public Domains(string domain) { this.Domain = domain; }
[PrimaryKey, MaxLength(64)]
public string Domain { get; set; }
}
public class Statistics
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Timestamp { get; set; }
[ForeignKey(typeof(Domains))]
public String DomainId { get; set; }
public int Status { get; set; }
[ManyToOne(CascadeOperations = CascadeOperation.All)]
public Domains Domain { get; set; }
}
My mistake, the feature "Foreign key" does not work as I thought. So I use view with join two tables and triggers for edit view.
And it works as I need it.
SQLite.Net and SQLite.Net.Extensions doesn't support views and triggers, so I use only SQLite.Net and Execute-function for create view and triggers.
CREATE VIEW IF NOT EXISTS 'StatisticsView' AS
SELECT Stat.Id, Stat.Timestamp, Dom.Domain, Stat.Status FROM Statistics AS Stat
INNER JOIN Domains Dom ON Stat.DomainId = Dom.Id
CREATE TRIGGER IF NOT EXISTS 'StatisticsViewInsert'
INSTEAD OF INSERT ON 'StatisticsView'
BEGIN
INSERT OR IGNORE INTO Domains(Domain) VALUES(NEW.Domain);
INSERT INTO Statistics(Timestamp, Status, DomainId) VALUES (NEW.Timestamp, NEW.Status, (SELECT Id FROM Domains WHERE Domain = NEW.Domain));
END
CREATE TRIGGER IF NOT EXISTS 'StatisticsViewUpdate'
INSTEAD OF UPDATE ON 'StatisticsView'
BEGIN
INSERT OR IGNORE INTO Domains(Domain) VALUES(NEW.Domain);
UPDATE Statistics SET Status = NEW.Status, Timestamp = NEW.Timestamp, DomainId = (SELECT Id FROM Domains WHERE Domain = NEW.Domain) WHERE Id = OLD.Id;
END
CREATE TRIGGER IF NOT EXISTS 'StatisticsViewDelete'
INSTEAD OF DELETE ON 'StatisticsView'
BEGIN
DELETE FROM Domains WHERE (Domain = OLD.Domain AND (SELECT COUNT(Id) FROM Statistics WHERE DomainId = (SELECT Id FROM Domains WHERE Domain = OLD.Domain)) < 2);
DELETE FROM Statistics WHERE Id=OLD.Id;
END
Tables classes:
public class Domains
{
public Domains() { }
public Domains(string domain) { this.Domain = domain; }
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[Unique, MaxLength(64)]
public string Domain { get; set; }
}
public class Statistics
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Timestamp { get; set; }
public int DomainId { get; set; }
public int Status { get; set; }
}
// Virtual Table
public class StatisticsView
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Timestamp { get; set; }
public string Domain { get; set; }
public int Status { get; set; }
}
Main code:
static void Main(string[] args)
{
var dbFile = "stats.db";
var domains = new[] { "stackoverflow.com", "superuser.com", "serverfault.com", "google.com", "microsoft.com" };
var statList = new List<StatisticsView>();
var sqlBase = new SQLiteConnection(dbFile);
sqlBase.CreateTable<Domains>();
sqlBase.CreateTable<Statistics>();
sqlBase.Execute("CREATE VIEW IF NOT EXISTS 'StatisticsView' AS SELECT Stat.Id, Stat.Timestamp, Dom.Domain, Stat.Status FROM Statistics AS Stat INNER JOIN Domains Dom ON Stat.DomainId=Dom.Id;");
sqlBase.Execute("CREATE TRIGGER IF NOT EXISTS 'StatisticsViewInsert' INSTEAD OF INSERT ON 'StatisticsView' BEGIN INSERT OR IGNORE INTO Domains(Domain) VALUES(NEW.Domain); INSERT INTO Statistics(Timestamp, Status, DomainId) VALUES (NEW.Timestamp, NEW.Status, (SELECT Id FROM Domains WHERE Domain=NEW.Domain)); END");
sqlBase.Execute("CREATE TRIGGER IF NOT EXISTS 'StatisticsViewUpdate' INSTEAD OF UPDATE ON 'StatisticsView' BEGIN INSERT OR IGNORE INTO Domains(Domain) VALUES(NEW.Domain); UPDATE Statistics SET Status=NEW.Status, Timestamp=NEW.Timestamp, DomainId=(SELECT Id FROM Domains WHERE Domain=NEW.Domain) WHERE Id=OLD.Id; END");
sqlBase.Execute("CREATE TRIGGER IF NOT EXISTS 'StatisticsViewDelete' INSTEAD OF DELETE ON 'StatisticsView' BEGIN DELETE FROM Domains WHERE (Domain = OLD.Domain AND (SELECT COUNT(Id) FROM Statistics WHERE DomainId=(SELECT Id FROM Domains WHERE Domain=OLD.Domain)) < 2); DELETE FROM Statistics WHERE Id=OLD.Id; END");
Console.WriteLine(SQLite3.LibVersionNumber());
var runTimestamp = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
foreach (var domain in domains)
{
HttpWebResponse resp = null;
var status = -1;
try
{
resp = (HttpWebResponse)WebRequest.Create("http://" + domain).GetResponse();
}
catch { };
status = (int)resp.StatusCode;
var stat = new StatisticsView();
stat.Domain = domain;
stat.Status = status;
stat.Timestamp = runTimestamp;
statList.Add(stat);
}
sqlBase.InsertAll(statList);
Console.WriteLine(@"Table ""Domains""");
foreach (var table in sqlBase.Table<Domains>())
{
Console.WriteLine("Id: {0}\tDomain: {1}", table.Id, table.Domain);
}
Console.WriteLine();
Console.WriteLine(@"Table ""Statistics""");
foreach (var table in sqlBase.Table<Statistics>())
{
Console.WriteLine("Id: {0}\tDomain Id: {1}", table.Id, table.DomainId);
}
Console.WriteLine();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
After second run:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.