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
我正在尝试建立一个数据库,其相关部分如下所示。我在 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