SQLite.Net 多对一扩展没有按预期工作

SQLite.Net Extensions with Many-To-One doesn't work as expected

我的 table 将包含许多重复的字符串,例如域。为了最小化数据库大小,我只想在 other table 中保存唯一域,并在 main table.

中使用域 id

我都是手动完成的,但不久前我发现 SQLite 可以自动完成。

现在我尝试使用 "FOREIGN" 键的多对一关系,但没有成功。 可能是我做错了。

示例代码

表类:

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; }
}

主要代码:

static void Main(string[] args)
{
    var dbFile = "stats.db";
    var domains = new[] { "whosebug.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();
}

第一次 运行 之后看起来不错。

但是在第二个 运行 之后,当域重复时 - sqlite 扩展插入错误的域 ID

我哪里弄错了?

在您的代码中,您每次尝试保存新统计数据时都会创建新的 Domains 实体。

SQLite-Net Extensions 需要引用对象的主键才能分配外键。您的 InsertOrIgnoreAllWithChildren 似乎正在将 10 分配给所有 Domains 对象,即使它们没有被插入也是如此。

您需要做的是获取您当前的域以获得正确的主键。

尝试这样的事情:

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[] { "whosebug.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);

您可以使用 Domain 作为主(和外)键,您当前的代码将按预期工作:

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; }
}

我的错误,功能 "Foreign key" 没有像我想的那样工作。 所以我使用视图连接两个表和触发器来编辑视图。

它能按我的需要工作。

SQLite.Net 和 SQLite.Net。扩展不支持视图和触发器,所以我只使用 SQLite.Net 和执行函数来创建视图和触发器。

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

表类:

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; }
}       

主要代码:

static void Main(string[] args)
{
    var dbFile = "stats.db";
    var domains = new[] { "whosebug.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();
}

秒后运行: