DBIx::Class has_one <-> might_have 关系

DBIx::Class has_one <-> might_have relationship

我正在尝试建立一个数据库,其相关部分如下所示。我在 Arch Linux 上使用 SQLite3 (3.8.8.3-1),DBIx::Class 0.082820.

它是简单簿记系统的一部分。一个invoice line has_one transaction,但是一个transaction只有might_have一个对应的invoice line(因为有些transaction可以创建没有发票)。

我无法DBIx::Class一次性插入发票行及其对应的交易记录。错误信息也在下面。

我做错了吗?或者做一些没有意义的事情?

为什么它首先搜索具有相同描述的现有交易?

以下是我大大简化的测试用例的详细信息:

InvoiceLine.pm:

package Test::DB::Schema::Result::InvoiceLine;

use strict;
use warnings;

use base 'DBIx::Class::Core';
__PACKAGE__->table("invoice_lines");
__PACKAGE__->add_columns(

  "id",
  { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },

  "txn_id",
  { data_type => "integer", is_foreign_key => 1, is_nullable => 0, 
    is_deferrable => 1 }, # tried this, but it doesn't help

  'details',
  { data_type => 'text', is_nullable => 0 },

);
__PACKAGE__->set_primary_key("id");

# Invoice line has an associated transaction
__PACKAGE__->has_one(
  "txn",
  "Test::DB::Schema::Result::Transaction",
  'id',
);

# Experimental -- this doesn't work either
#__PACKAGE__->belongs_to(
#  "txn",
#  "Test::DB::Schema::Result::Transaction",
#  "txn_id",
#);

1;

Transaction.pm:

use utf8;
package Test::DB::Schema::Result::Transaction;

use strict;
use warnings;

use base 'DBIx::Class::Core';
__PACKAGE__->table("transactions");
__PACKAGE__->add_columns(
    "id",
    { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },

    'description',
    { data_type => 'text', is_nullable => 0 },

    # Invoice line 
    # Null if no associated invoice
    'invoice_line_id',
    {data_type => 'integer', is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");

# Some transactions have a single corresponding
# invoice line
__PACKAGE__->might_have(
    "invoice_line",
    "Test::DB::Schema::Result::InvoiceLine",
    'id',
    { cascade_copy => 0, cascade_delete => 0 },
);
# EXPERIMENTAL == this doesn't work either
# might_have isn't working, so try has_many (where many can be 0):
#__PACKAGE__->has_many(
#   'invoice_lines',
#   "Test::DB::Schema::Result::InvoiceLine",
#   'txn_id',
#);

1;

Test.pl

#!/usr/bin/perl
# Test.pl
# Testing might_have <-> has_one relationship
use Test::DB::Schema;
my $schema = Test::DB::Schema->connect(
    "dbi:SQLite:dbname=dbic_test.db", '', '', {}
);
$schema->deploy({ add_drop_table => 1 } , '.');
$schema->storage->debug(1);
my $data1 = {
    details => 'abc',
    txn => {
        description => 'xyz',
    }
};
my $new1 = $schema->resultset('InvoiceLine')->create($data1);

运行Test.pl的结果是:

BEGIN WORK
SELECT me.id, me.description, me.invoice_line_id FROM transactions me WHERE ( me.description = ? ): 'xyz'
INSERT INTO transactions ( description) VALUES ( ? ): 'xyz'
INSERT INTO invoice_lines ( details, id) VALUES ( ?, ? ): 'abc', '1'
DBIx::Class::Storage::DBI::_dbh_execute(): DBI Exception: DBD::SQLite::st execute failed: NOT NULL constraint failed: invoice_lines.txn_id [for Statement "INSERT INTO invoice_lines ( details, id) VALUES ( ?, ? )"] at ./Test.pl line 16
DBIx::Class::Storage::TxnScopeGuard::DESTROY(): A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back. at /usr/share/perl5/site_perl/DBIx/Class/Exception.pm line 77
ROLLBACK

错误的关系定义。使用这个:

# InvoiceLine.pm
__PACKAGE__->might_have(
    "txn",
    "Test::DB::Schema::Result::Transaction",
    "invoice_line_id",
);

# Transaction.pm
__PACKAGE__->belongs_to(
    "invoice_line",
    "Test::DB::Schema::Result::InvoiceLine",
    "invoice_line_id",
);

感谢 Denis Ibaev 的回答,我重新解决了这个问题,并找到了一个很好的解决方案。

事实上,我需要发票行具有has_one关系而不是might_have,但这只是一个微小的变化。

将事务的关系从 might_have 更改为 belongs_to 是重要的一点。

我还必须在发票行中手动输入 txn_id。

这是我的新代码:

InvoiceLine.pm:

package Test::DB::Schema::Result::InvoiceLine;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table("invoice_lines");
__PACKAGE__->add_columns(
    "id",
    { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
    "txn_id",
    { data_type => "integer", is_foreign_key => 1, is_nullable => 1 }, # because , is_deferrable => 1 }, doesn't work with SQLite
    'details',
    { data_type => 'text', is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
# Invoice line has an associated transaction
__PACKAGE__->has_one(
    "txn",
    "Test::DB::Schema::Result::Transaction",
    'invoice_line_id',
);
1;

Transaction.pm:

package Test::DB::Schema::Result::Transaction;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table("transactions");
__PACKAGE__->add_columns(
    "id",
    { data_type => "integer", is_auto_increment => 1, is_nullable => 0 },
    'description',
    { data_type => 'text', is_nullable => 0 },
    # Invoice line 
    # Null if no associated invoice
    'invoice_line_id',
    {data_type => 'integer', is_nullable => 1 }, 
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to(
    "invoice_line",
    "Test::DB::Schema::Result::InvoiceLine",
    'invoice_line_id', # our_fk_column
);
1;

Test.pl:

my $schema = Test::DB::Schema->connect(
    "dbi:SQLite:dbname=dbic_test.db", '', '', {}
);
$schema->deploy({ add_drop_table => 1 } , '.');
$schema->storage->debug(1);
my $data1 = {
    details => 'abc',
    txn => {
        description => 'xyz',
    }
};
$schema->txn_do(sub {
    my $new1 = $schema->resultset('InvoiceLine')->create($data1);
    # add the reverse link
    $new1->txn_id($new1->txn->id);
    $new1->update;
}); # end of txn_do
# Add another one with the same data to make sure
# they end up as separate rows
$schema->txn_do(sub {
    my $new2 = $schema->resultset('InvoiceLine')->create($data1);
    $new2->txn_id($new2->txn->id);
    $new2->update;
}); # end of txn_do

运行 Test.pl 生成并运行这个 SQL:

BEGIN WORK
INSERT INTO invoice_lines ( details) VALUES ( ? ): 'abc'
INSERT INTO transactions ( description, invoice_line_id) VALUES ( ?, ? ): 'xyz', '1'
UPDATE invoice_lines SET txn_id = ? WHERE ( id = ? ): '1', '1'
COMMIT
BEGIN WORK
INSERT INTO invoice_lines ( details) VALUES ( ? ): 'abc'
INSERT INTO transactions ( description, invoice_line_id) VALUES ( ?, ? ): 'xyz', '2'
UPDATE invoice_lines SET txn_id = ? WHERE ( id = ? ): '2', '2'
COMMIT

现在表格包含正确的值:

Invoice Lines       
1|1|abc
2|2|abc

Transactions
1|xyz|1
2|xyz|2