I have something like this:
mapping (address => mapping(string => uint)) m_Map;
It can be accessed as
m_Map[strCampaignName][addrRecipient], campaign can have multiple recipients...
Now at some point (ICO failed), I need to remove that campaign with all recipients. I don't think a simple
delete m_Map[strCampaignName] will work.
If I use
m_Map[strCampaignName] = null, I think data will not be deleted.
If I iterate through a list of all recipients, I will run out of gas.
How should this situation be handled?
Min: I want m_Map[strCampaignName] to be empty,
Max: I want to stop wasting memory on it.
As you stated, you can't delete a mapping in Solidity. The only way to "clear" the data is to iterate through the keys (using a separate array that stores the keys) and delete the individual elements. However, you're correct to be concerned about the cost...Depending on the size of the mapping, you could run into gas consumption issues.
A common approach to work around this is to use a struct in your mapping with a soft delete:
struct DataStruct {
mapping(string => uint) _data;
bool _isDeleted;
}
mapping(address => DataStruct) m_Map;
Now, deleting an entry just requires you to set the flag: m_Map[someAddr]._isDeleted = true;
If you have all the addrRecipient of the mapping
delete m_Map[strCampaignName][addrRecipient];
works.
Related
In the official docs I read:
Do bear in mind that virtual fields cannot be used in finds. If you want them to be part of JSON or array representations of your entities, see Exposing Virtual Fields.
It's not clear to me if the second sentence is in someway related to the first one - say as a workaround to overcome the limitation - or they are completely independent.
I mean: if I expose a Virtual Field then may I use it in a find statement?
Is there a way to include a virtual field in a query? Here a real example:
ItemOrdersTable.php:
$this->setTable('item_orders');
$this->setDisplayField('summary'); // virtual field
$this->setPrimaryKey('id');
Entity:
protected $_virtual = [
'summary'
];
protected function _getSummary()
{
return $this->name . ' ' . $this->description;
}
Usage in a Controller:
return TableRegistry::get('itemOrders')->find('list')->where(['order_id' => $id]);
Because I specified 'summary' as DisplayField, I'm expecting a key-value list of all records that meet the where clause, with the id as key and the summary virtual field as value. Because this doesn't happen (the returned object is null) I'm trying to understand if my code is wrong or I didn't read correctly the documentation as asked above.
Customize Key-Value Output:
https://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html#customize-key-value-output
Update:
$results = TableRegistry::getTableLocator()->get('item_orders')
->find('list')
->where(['order_id' => $id]);
debug($results->toArray());
$this->set('orders', $results);
debug($orders); exit; <-- test results, and post in your question.
Since this mornong i am facing a very big problem. I am using CodeIgniter to develop a website, and GAS ORM for the database.
I have basically two tables. One named "pool", and one named "partners". I am having two associations between these two tables, so I have two foreign keys in my table Partners referencing the table pool.
Pool(#id:integer, name:varchar)
Partners(#id:integer, associated_pool_id=>Pool, futur_associated_pool_id=>Pool).
As I have two references to the same table, I can't name the foreign keys "pool_id". So in my relationships with Gas ORM, I have to specify the names of the columns. I do it, but it doesn't work...
Here is what I do:
class Partner extends ORM {
public $primary_key = 'id';
public $foreign_key = array('\\Model\\Pool' => 'associated_pool_id', '\\Model\\Pool' => 'future_associated_pool_id');
function _init()
{
// Relationship definition
self::$relationships = array(
'associated_pool' => ORM::belongs_to('\\Model\\Pool'),
'future_association_pool' => ORM::belongs_to('\\Model\\Pool'),
);
self::$fields = array(
'id' => ORM::field('auto[11]'),
'name' => ORM::field('char[255]'),
'associated_pool_id' => ORM::field('int[11]'),
'future_associated_pool_id' => ORM::field('int[11]')
);
}
and in my Pool class :
class Pool extends ORM {
public $primary_key = 'id';
function _init()
{
// Relationship definition
self::$relationships = array(
'associated_partner' => ORM::has_many('\\Model\\Partner'),
'future_associated_partner' => ORM::has_many('\\Model\\Partner'),
);
self::$fields = array(
'id' => ORM::field('auto[11]'),
'name' => ORM::field('char[50]'),
);
}
I have a test controller testing if everything is okay:
class Welcome extends CI_Controller {
public function index()
{
$pool = \Model\Pool::find(1);
echo $pool->name;
$partners = $pool->associated_partner();
var_dump($partners);
}
But I have an error saying:
Error Number: 1054
Champ 'partner.pool_id' inconnu dans where clause
SELECT * FROM partner WHERE partner.pool_id IN (1)
I don't know how to specify to Gas ORM that it shouldn't take "pool_id" but "associated_pool_id"....
Thank you for your help!!!!!!!!!!!!
I don't know, if this topic is still up to date and interesting to some of you, but in general, I had the exact same problem.
I decided Gas ORM to be my mapper in combination with CodeIgniter. As my database structure was given and it was not following the table_pk convention of Gas, I had to define a foreign key by myself which shall refer to my custom database foreign key. However, the definition of it had no impact on anything. Like your error above, the mapper was not able to build the right SQL-statement. The statement looked similar to yours:
SELECT * FROM partner WHERE partner.pool_id IN (1)
Well, it seems like Gas ignores the self-defined foreign keys and tries to use the default table_pk convention. This means, it takes the table (in your case: pool) and the primary key (id) by merging it with a underscore character.
I figured out, that the constructor of orm.php handles every primary and foreign key defined within the entities. In line 191, the code calls an if clause combined with the empty function of php. As the primary key is defined always and there is no negation in the statement, it skips the inner part of the clause every time. However, the inner part takes care of the self-defined foreign keys.
Long story short, I added a negation (!) in line 191 of orm.php which leads me to the following code:
if ( ! empty($this->primary_key))
{
if ( ! empty($this->foreign_key))
{
// Validate foreign keys for consistency naming convention recognizer
$foreign_key = array();
foreach($this->foreign_key as $namespace => $fk)
{
$foreign_key[strtolower($namespace)] = $fk;
}
$this->foreign_key = $foreign_key;
}
else
{
// If so far we didnt have any keys yet,
// then hopefully someone is really follow Gas convention
// while he define his entity relationship (yes, YOU!)
foreach ($this->meta->get('entities') as $name => $entity)
{
if ($entity['type'] == 'belongs_to')
{
$child_name = $entity['child'];
$child_instance = new $child_name;
$child_table = $child_instance->table;
$child_key = $child_instance->primary_key;
$this->foreign_key[strtolower($child_name)] = $child_table.'_'.$child_key;
}
}
}
}
Well, this little fix helped me out a lot and I hope some of you can take advantage of this hint as well.
I want to expose a list of services from my DB or just return one service detail via Web API with my EF DBmodel . I used VS2012 Web API scaffolding, quite easy so far and it works and return the list of services in JSON when I hit the URL(.../api/Services). The problem is that when I want to obtain just one service URL(.../api/Services/1), I still obtain the full list of all services although when I trace it seems to return only a count of 1 object.
What happening here?
Here are the 2 controller actions.
ps: I also tried using a .Where() instead of .Find() but the result is the same in both cases.
// GET api/Services
public IEnumerable<service> Getservices()
{
var services = db.services.Include(s => s.Category).Include(s => s.Country).Include(s => s.StateProvince).Include(s => s.Territory);
return services.AsEnumerable();
}
// GET api/Services/5
public service Getservice(int id)
{
service service = db.services.Find(id);
if (service == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return service;
}
Try handling it as: var service = db.services.Single(s => s.Id == id)
First, check if your database has a single item for your query or not.
If you are querying by primary key then
db.services.SingleOrDefault(s => s.Id == id) should do.
You will need to handle the exception if you are querying on some field which may give back more than one result.
The variant of filtering (Single, SingleOrDefault, First, FirstOrDefault) that you use will depend upon the exact semantics of the code.
Table.linkedIndex is related to LinkedIndex.ID. The value of the field LinkedIndex.TableName is either Linked1 or Linked2 and defines which of these tables is related to a row in Table.
Now i want to make a dynamical link with Yii models so that i can easily get from a Table row to the corresponding Linked1 or Linked2 row:
Table.linkedID = [LinkedIndex.TableName].ID
Example
Table values:
LinkedIndex values:
Now I should get the row from Linked2 where ID=2:
$model = Table::model()->findByPk(0);
$row = $model->linked;
Model
In the model Table, I tried to make the relation to the table with the name of the value of linkedIndex.TableName:
public function relations()
{
return array(
'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
'linked' => array(
self::HAS_ONE,
'linkedIndex.TableName',
array('ID' => 'linkedID'),
)
)
}
But then I get the error:
include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory
Is there any way to make a dynamic relation Table.linkedID -> [LinkedIndex.TableName].ID with Yii Models?
Per the Yii docs here:
http://www.yiiframework.com/doc/api/1.1/CActiveRecord#relations-detail
I'd suggest using self::HAS_ONE instead (unless there can be multiple rows in LinkedIndex with the same ID - although from the looks of above, I doubt that's the case).
You can link tables together that have different keys by following the schema:
foreign_key => primary_key
In case you need to specify custom PK->FK association you can define it as array('fk'=>'pk'). For composite keys it will be array('fk_c1'=>'pk_с1','fk_c2'=>'pk_c2').
so in your case:
public function relations(){
return array(
'linkedIndex' => array(self::HAS_ONE, 'LinkedIndex', array('ID' => 'linkedIndex')),
);
}
where LinkedIndex is the class name for the LinkedIndex model (relative to your Table model - i.e. same folder. You could change that, of course) and array('ID' => 'linkedIndex') specifies the relationship as LinkedIndex.ID = Table.linkedIndex.
Edit
Looking at your updated example, I think you're misunderstanding how the relations function works. You're getting the error
include(linkedIndex.TableName.php) [function.include]: failed to open stream: No such file or directory
because you're trying to create another relation here:
'linked' => array(
self::BELONGS_TO,
'linkedIndex.TableName',
array('ID' => 'linkedID'),
)
This part: linkedIndex.TableName refers to a new model class linkedIndex.TableName, so Yii attempts to load that class' file linkedIndex.TableName.php and throws an error since it doesn't exist.
I think what you're looking for is to be able to access the value TableName within the table LinkedIndex, correct? If so, that's accessible from within the Table model via:
$this->linkedIndex->TableName
This is made possible by the relation we set up above. $this refers to the Table model, linkedIndex refers to the LinkedIndex relation we made above, and TableName is an attribute of that LinkedIndex model.
Edit 2
Per your comments, it looks like you're trying to make a more complex relationship. I'll be honest that this isn't really the way you should be using linking tables (ideally you should have a linking table between two tables, not a linking table that says which 3rd table to link to) but I'll try and answer your question as best as possible within Yii.
Ideally, this relationship should be made from within the LinkedIndex model, since that's where the relationship lies.
Since you're using the table name as the linking factor, you'll need to create a way to dynamically pass in the table you want to use after the record is found.
You can use the LinkedIndex model's afterFind function to create the secondary link after the model is created within Yii, and instantiate the new linked model there.
Something like this for your LinkedIndex model:
class LinkedIndex extends CActiveRecord{
public $linked;
public static function model($className = __CLASS__){
return parent::model($className);
}
public function tableName(){
return 'LinkedIndex';
}
public function afterFind(){
$this->linked = new Linked($this->TableName);
parent::afterFind();
}
//...etc.
}
The afterFind instantiates a new Linked model, and passes in the table name to use. That allows us to do something like this from within the Linked model:
class Linked extends CActiveRecord{
private $table_name;
public function __construct($table_name){
$this->table_name = $table_name;
}
public static function model($className = __CLASS__){
return parent::model($className);
}
public function tableName(){
return $this->table_name;
}
//...etc.
}
which is how we dynamically create a class with interchangeable table names. Of course, this fails of the classes need to have separate operations done per-method, but you could check what the table_name is and act accordingly (that's pretty janky, but would work).
All of this would result in being to access a property of the linked table via (from within the Table model):
$this->linkedIndex->linked->foo;
Because the value of LinkedIndex.TableName and Table.linkedID is needed to get the values, I moved the afterFind, suggested by M Sost, directly into the Table-Class and changed its content accordingly. No more need for a virtual model.
class Table extends CActiveRecord {
public $linked; // Needs to be public, to be accessible
// ...etc.
public function afterFind() {
$model = new $this->linkedIndex->TableName;
$this->linked = $model::model()->findByPk( $this->linkedID );
parent::afterFind();
}
// ...
}
Now I get the row from Linked2 where ID=2:
$model = Table::model()->findByPk(0);
$row = $model->linked;
I use LINQ 2 SQL in one of my projects and i have numerous relationships Customer -> Documents1, Documents2, Documents3, Address, Invoices etc....
When using the LoadWith(p => p.Documents1)...etc
I have performance issues, imagine 2000 customers with all these numerous relationships loaded in List in memory!
The other way around Document -> Customer its not so much an issue as the relationship its light.
So i try to remove all the LoadWith and just leave the Customer -> Address relationship.
Now if i go and open a Document1 and then open my Customers i get an object disposed exception when i serialize my Customers. The serialize method basically throws this exception.
The serialize Method:
public static T CloneObjectGraph<T>(this T obj) where T : class
{
var serializer = new DataContractSerializer(typeof(T), null, int.MaxValue, false, true, null);
using (var ms = new System.IO.MemoryStream())
{
serializer.WriteObject(ms, obj);
ms.Position = 0;
return (T)serializer.ReadObject(ms);
}
}
The Method i get the Customers:
public List<Customer> GetCustomers()
{
using (MyDataContext db = new MyDataContext(MyDataContextManager.ConnectionString))
{
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Customer>(p => p.Address);
dlo.LoadWith<Customer>(p => p.Documents1);
dlo.LoadWith<Customer>(p => p.Documents2);
dlo.LoadWith<Customer>(p => p.Documents3);
dlo.LoadWith<Customer>(p => p.Documents4);
dlo.LoadWith<Customer>(p => p.Documents5);
dlo.LoadWith<Customer>(p => p.Documents6);
dlo.LoadWith<Customer>(p => p.Documents7);
dlo.LoadWith<Customer>(p => p.Documents8);
dlo.LoadWith<Customer>(p => p.Documents9);
dlo.LoadWith<Customer>(p => p.Documents10);
dlo.LoadWith<Customer>(p => p.Documents11);
db.LoadOptions = dlo;
return db.Customers.ToList();
}
}
I want to remove all the LoadWith except Address relationship.
I hate when this error is not reproduce always but in some cases i couldn't find.
I could guess the DataContractSerializer constructor for a change but i cant get it right.
Any help appreciated!
Your error is occurring because your clone method is going to attempt to access all of the child properties of your object. When you have the LoadWith<>() statements, those child properties are already retrieved from the database and available in memory. When you remove the LoadWith<>() statements, the properties will attempt to Lazy Load the objects from the database. Because you've already closed the database connection, those properties cannot be loaded (and you get the error).
The solution depends on what you are attempting to accomplish by performing a deep copy of the objects; If that deep copy actually does need to have the child properties (DocumentsXX), then you either have to leave the LoadWith<>() statements or you need the database connection to stay open during the process (of the two, leaving the LoadWith<>() statements is probably the better option because it minimizes the accesses to the DB and ends up using the same amount of memory anyway). If the deep copy really doesn't need to include those properties, then you need to replace the current deep clone process with on that can ignore those properties (either a manually created deep clone or a customized serialization process would work).