User defined PK on yii2 - yii2

I am currently following the Yii2 start guide.
I am stumpted at the interacting with a database step.
I have created a database as they suggest with code as its primary key
CREATE TABLE `country` (
`code` CHAR(2) NOT NULL PRIMARY KEY,
`name` CHAR(52) NOT NULL,
`population` INT(11) NOT NULL DEFAULT '0'
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
I create a model and the CRUD methods using the gii generator.
The issue is that the generated code refers to id as a primary key at all times, not the case due to the suggested table structure, and even the findModel needs to be updated from.
protected function findModel($code)
{
if (($model = Country2::findOne($id)) !== null) {
return $model;
}
throw new NotFoundHttpException('The requested page does not exist.');
}
To
protected function findModel($code)
{
if (($model = Country2::findOne($code)) !== null) {
return $model;
}
throw new NotFoundHttpException('The requested page does not exist.');
}
Once updated the code works but the issue is with the gridview where every action has an id tacked on to it.
ie https://localhost/tutorialYii/web/index.php?r=country2%2Fview&id=12
rather than https://localhost/tutorialYii/web/index.php?r=country2%2Fview&code=12
I understand this can be fixed by using id as the primary key but I would like to understand how to address the issue of using a custom primary key rather than the standard one.
I have not modified the autogenerated code in any form.
The autogenerated code works for country2 which has id as its PK. I would like to understand how to adapt it so that it works for a PK that is not id.

Implement primarykey() in your model:
public static function primaryKey()
{
return ['code'];
}
Now you can use this:
YourModel::findOne($code);
and always use $model->getPrimarykey() instead $model->code for access to your primary key.

Related

Aurora MySQL Write Forwarding + EntityFramework; Concurrency exception and timeouts

we have an existing .net application that makes heavy use of EntityFramework (6.4.4), as well as some usage of Dapper (2.0.35) both with MySql.Data provider (8.0.23).
we are in the process of exploring how to move to a global RDS Aurora database with write forwarding, and i have been working with a minimal simulation of our application.
so far in the secondary region, inserting data with EntityFramework always fails on SaveChanges; if connection pooling is off: DbUpdateConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0) (i assume the concurrency here is the write-forwarding, as i am the only one using this test database). if connection pooling is on: with a timeout (i think) trying to read the affected rows.
using dapper (as a comparison), running INSERT INTO...; SELECT last_insert_id(); or INSERT INTO...; DO SLEEP(2); SELECT last_insert_id(); in a single call always returns an id of 0, while running INSERT INTO ...; in one call and then SELECT last_insert_id(); in a second call produces the correct id.
changing the value of aurora_replica_read_consistency doesn't seem to make a difference.
(in the primary region, everything works as expected - no timeouts or errors and the last_insert_id is correct)
i've been trying to play with settings, but i haven't really made any progress
i have opened an aws support case as well, but i was wondering if anyone has examples of a .net application working with write forwarding for a global aurora mysql database? especially with entityframework?
or knows of any gotchas or tips on where to focus an investigation?
=====================================
table creation:
CREATE TABLE IF NOT EXISTS `data` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Key` varchar(100) NOT NULL,
`Value` varchar(45) DEFAULT NULL,
`Date` datetime DEFAULT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `key_UNIQUE` (`Key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
database entity:
[Table("data")]
public class SavedData
{
[Key]
public int ID { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public DateTime Date { get; set; }
}
context:
[DbConfigurationType(typeof(MySqlEFConfiguration))]
public class DatabaseContext : DbContext, IDbContext
{
public DatabaseContext()
{
Configuration.LazyLoadingEnabled = false;
Configuration.UseDatabaseNullSemantics = true;
Database.SetInitializer<DatabaseContext>(null);
}
public virtual DbSet<SavedData> SavedData { get; set; }
}
access:
using var dbContext = new DatabaseContext();
var dbEntity = new Entities.SavedData
{
Key = toSave.Key,
Value = toSave.Value,
Date = DateTime.UtcNow
};
dbContext.SavedData.Add(dbEntity);
dbContext.SaveChanges();
Console.WriteLine($"updated entity id {dbEntity.ID}");
return dbEntity.ID;
the connection string looks like (connection pooling off)
password=*****;User Id=failover_user;server=********;port=3306;database=failover_test;Pooling=false;
or (connection pooling on)
password=*****;User Id=failover_user;server=********;port=3306;database=failover_test;MinimumPoolSize=1;maximumpoolsize=5;

Yii2 REST API update (put) when composite key

I am trying to update my model via PUT http request in Yii2 framework.
Everything works fine when I have single Primary Key in my model.
Problems are when I have composite primary key in table.
How to update?
I submit JSON:
{"date_execution":"2017-08-26","order_id":"59", "company_id":13,"your_price":100,"car_id":"8","note":"lorem ipsum"}
my composite primary key include:
- order_id
- company_id
I tried following requests:
PUT SERVER/offer/100 - where 100 is company_id
PUT SERVER/offer/2000 - where 2000 is order_id
those 2 requests are returning problem:
{"name":"Not Found","message":"Object not found: 13","code":0,"status":404,"type":"yii\\web\\NotFoundHttpException"}
I also tried
PUT SERVER/offer/2000/100 - where 2000 is order_id and 100 is company_id
PUT SERVER/offer/100/2000
those 2 return controller/action not found exception
Also I added order_id and company_id to JSON,
but nothing works.
Controller Class:
use yii\rest\ActiveController;
class OfferController extends ActiveController
{
// adjust the model class to match your model
public $modelClass = 'app\models\Offer';
public function behaviors(){
$behaviors = parent::behaviors();
// remove authentication filter
$auth = $behaviors['authenticator'];
unset($behaviors['authenticator']);
// add CORS filter
$behaviors['corsFilter'] = [
'class' => CustomCors::className()
];
// re-add authentication filter
$behaviors['authenticator'] = [
'class' => CompositeAuth::className(),
'authMethods' => [
HttpBearerAuth::className(),
],
];
// avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
$behaviors['authenticator']['except'] = ['options'];
return $behaviors;
}
}
It should work if you use PUT SERVER/offer/2000,100
You can print primaryKey() of the model to know the order of the keys.
You can see it in the docs here
https://www.yiiframework.com/doc/api/2.0/yii-rest-action
If composite primary key, the key values will be separated by comma.
yii\rest\UpdateAction uses ActiveRecord::findModel() method to load data. There is an answer in those phpdoc:
If the model has a composite primary key, the ID must be a string of
the primary key values separated by commas
So the right resource is (considering the first key field in the table structure is company_id)
PUT SERVER/offer/100,2000
You firstly need to add primaryKey() in the model, to override the default primaryKey() of the ActiveRecord class. This function needs to return your composite primary key.
What you need to do the model thus would be
primaryKey()
{
return array('company_id', 'order_id');
}

CakePHP 3.0-RC1 throws fatal error from index method in Baked controller

I'm testing CakePHP 3.0-RC1 for possible use on a new project. After installing, configuring and creating two (yes, two) database tables, I ran 'bake all' against both tables.
After dealing with the spurious foreign key references in the model (a foreign key defined as the primary key referencing itself? C'mon, now, Bakers!) I am hitting this error:
Error: Method Cake\ORM\Query::__toString() must not throw an exception
File /srv/www/htdocs/wmcb/cake/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php
Line: 193
Not my code. ;)
The offending table definition:
-- -----------------------------------------------------
-- Table `ISOCountryCodes`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `iso_country_codes` (
`iso_country_code_id` VARCHAR(4) CHARACTER SET utf8 NOT NULL ,
`iso_country_name` VARCHAR(64) CHARACTER SET utf8 NOT NULL ,
`iso_country_name_french` VARCHAR(64) CHARACTER SET utf8 NOT NULL ,
PRIMARY KEY (`iso_country_code_id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8
COMMENT = 'Look-up table of ISO 3166-1 Country Names and Codes'
;
The IsoCountryCodesController index method, as generated by bake:
/**
* Index method
*
* #return void
*/
public function index()
{
$this->paginate = [
'contain' => ['IsoCountryCodes']
];
$this->set('isoCountryCodes', $this->paginate($this->IsoCountryCodes));
$this->set('_serialize', ['isoCountryCodes']);
}
And the initialize method from IsoCountryCodesTable.php:
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
$this->table('iso_country_codes');
$this->displayField('iso_country_code_id');
$this->primaryKey('iso_country_code_id');
// $this->belongsTo('IsoCountryCodes', ['foreignKey' => iso_country_code_id']);
}
with the reflexive foreign key commented out.
This behaviour holds for both tables.
CakePHP 2.5 works correctly with the same database table definitions.
Remove the 'contain' part in the paginate array. You don't have any association to IsoCountryCodes
The reason you got that result when baking is that bake is a conventions-based too. It can only try doing its best based on the stablished conventions. One of those conventions is that any column ending in _id is a foreignKey to another table.
Another convention is that the primary keys of tables should be named id. When not following the conventions you will need to help bake figuring out or fixing its guessing errors in the code it generates.

Cakephp save array data in MySql Database using loop with create

I am facing strange problem first time. I am trying to insert simple records in database with Custom Auto Increment Values e.g 00001, 00002, 00003. but unable to get Incremented value. Each record get updated with same max number.
Controller Code
public function dobulk() {
for($i=0;$i<5;$i++) {
$data = array();
$this->Tmp->create();
$data['Tmp']['invoice_no'] = $this->Tmp->get_no();
$data['Tmp']['invoice_date'] = '2013-12-11';
$this->Tmp->save($data);
}
}
Model Code
public function get_no() {
$strSql = "SELECT
LPAD(IFNULL(RIGHT(MAX(invoice_no),5),0) + 1,5,'0') AS max_id
FROM tmps
;";
$result = $this->query($strSql);
$invoice_no = $result[0][0]['max_id'];
return $invoice_no;
}
Database Table
CREATE TABLE `tmps` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`invoice_no` varchar(20) DEFAULT NULL,
`invoice_date` date DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1
Thanks in advance.
You're probably running into the model cache.
From here: Bakery article on caching
If Model->cacheQueries == true, Cake will store the result of queries in memory. If it later sees an identical query, the result will be pulled from memory instead of hitting the database. This is only cached for the duration of a single page request. However, if a record is updated, the cache is not cleared. This is what gets most people unfamiliar with the cache.
So to make your code work, add this line to the top of the dobulk() function:
$this->Tmp->cacheQueries = false;
As you're running raw SQL inside your model, you might also have to change the query request inside get_no() to:
$result = $this->query($strSql, false);
Hope below solution will help you!
public function get_no() {
$strSql = "SELECT MAX(invoice_no) AS max_id FROM tmps;";
$result = $this->query($strSql);
$invoice_no = $result['Tmp']['max_id'];
return $invoice_no;
}
public function dobulk(){
$data = array();
$tempInvoiceNumber = $this->Tmp->get_no();
for($i=1;$i<=5;$i++) {
$data[$i]['Tmp']['invoice_no'] = tempInvoiceNumber+$i;
$data[$i]['Tmp']['invoice_date'] = '2013-12-11';
}
$this->Tmp->saveAll($data);
}

Hibernate saving self Reference parent/child

I'm using Spring 3.2, Hibernate 4 and MySQL. I have a self referencing class called Lecturers which has annotations implementing a parent/child one to many relationship. I have a problem with implementing a controller and form for saving a parent and child from the same table. It's a self-referencing class.
My DB:
CREATE TABLE `lecturers` (
`lecturer_id` BIGINT(10) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NULL DEFAULT NULL,
`email` VARCHAR(255) NULL DEFAULT NULL,
`checker_id` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`lecturer_id`),
FOREIGN KEY (`checker_id`) REFERENCES `lecturers` (`lecturer_id`)
The Java class
#ManyToOne
#JoinColumn(name="checker_id")
private Lecturer checker;
#OneToMany(cascade = CascadeType.ALL, mappedBy="checker", orphanRemoval=true)
private List<Lecturer> lecturers = new ArrayList<Lecturer>();
And the class also has this method
#Transient
public void addLecturer(Lecturer lecturer) {
if(lecturers == null) {
lecturers = new ArrayList<Lecturer>();
//lecturers = new HashSet<Lecturer>();
}
lecturer.setChecker(this);
lecturer.setLecturers(lecturers);
//lecturer.setLecturers(lecturers);
lecturers.add(lecturer);
// TODO Auto-generated method stub
}
I then set up a DAO and Service layer for implementing a CRUD operations. The create method is this:
Session session = sessionFactory.getCurrentSession();
transaction = session.beginTransaction();
// Create new lecturers
Lecturer lecturer1 = new Lecturer();
lecturer1.setName(name);
lecturer1.setEmail(email);
Lecturer lecturer2 = new Lecturer();
lecturer2.setName(name);
lecturer2.setEmail(email);
// Create new checker
Lecturer checker = new Lecturer();
checker.setName(name);
checker.setEmail(email);
checker.setChecker(checker);
List<Lecturer> lecturers = new ArrayList<Lecturer>();
lecturers.add(lecturer1);
lecturers.add(lecturer2);
lecturer1.setChecker(checker);
lecturer2.setChecker(checker);
checker.addLecturer(lecturer1);
checker.addLecturer(lecturer2);
checker.setLecturers(lecturers);
session.save(checker);
session.save(lecturer1);
session.save(lecturer2);
My requirement is now to provide a form that will be used to match a parent (Checker) to one or more children (Lecturers) and save the match to the database. I'm asking how I should go about saving the relationship. Should I create the parent and children separately, then match a parent using the id to a children selected from say a drop down list? I'm not sure how to make sure the relationship between a checker and its respective lecturers is saved.
I then created a main class for testing the relationship and to see if it works. Inserting data into the db works but when I want to list it I get this:
Name: Mark
Email: ma#msn.com
Checker: com.professional.project.domain.Lecturer#439942
ID: 22
I should get the name of the checker back which I already added but it's not coming back.
I would appreciate some help on how to proceed.
First of all, your addLecturer() method has a bug. It shouldn't set the lecturers list of the child to the current lecturer's list:
public void addLecturer(Lecturer lecturer) {
if (lecturers == null) {
lecturers = new ArrayList<Lecturer>(); // OK : lazy initialization
}
lecturer.setChecker(this); // OK : set the parent of the child to this
lecturer.setLecturers(lecturers); // this line should be removed : the child's children shouldn't be the same as this lecturer's children
lecturers.add(lecturer); // OK : ad the child to the list of children
}
When you get a lecturer, you obtain the following as the checker :
Checker: com.professional.project.domain.Lecturer#439942
The above is just the result of the call to the default toString() method on the checker. To get its name, call getName() on the checker. If you want the toString() method to return the name, then implement it that way:
#Override
public String toString() {
return name;
}