【问题标题】:DBIx::Class has_one <-> might_have relationshipDBIx::Class has_one <-> might_have 关系
【发布时间】:2015-04-21 03:13:54
【问题描述】:

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

这是一个简单的簿记系统的一部分。一个发票行has_one 交易,但一个交易只有可能有一个对应的发票行(因为有些交易可以在没有发票的情况下创建)。

我无法让 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

【问题讨论】:

    标签: perl sqlite dbix-class


    【解决方案1】:

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

    # 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",
    );
    

    【讨论】:

    • 谢谢你的回答——我明天试试。
    【解决方案2】:

    感谢 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
    

    Running 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
    

    【讨论】:

    • 总结一下这教会了我什么:has_many、has_one 和 might_have 关系(几乎)总是适用于 parent->child 方向; belongs_to 适用于另一个方向。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-29
    • 1970-01-01
    • 1970-01-01
    • 2011-07-09
    • 1970-01-01
    • 2016-08-25
    • 2023-03-18
    相关资源
    最近更新 更多