I'm somewhat confused about the following snipped of code which simply checks if a column exists in the table before adding it to the Model with addField().
strangely however, PHP error_log warns me that the public variable is not properly defined the first time I use it:
/var/www/ocmal_db_svn/lib/Model/Conflicto.php:[Notice] Undefined property: Model_Conflicto::$entity_code,
which is why the SQL query throws an error because 'SHOW columns FROM ' is incomplete without the table name...
defining a public variable within the class should initialize it, no?
or is something else going wrong? can anyone see an error here?
what am I overlooking? (this is PHP 5.3 BTW)
class Model_Conflicto extends Model_Table {
public $entity_code='conflicto';
function init(){
parent::init();
if ($this->api->db->dsql()->expr("SHOW COLUMNS FROM `". $this->entity_code. "` LIKE 'lock_id'")->getOne()==null)
error_log('OCMAL_DB: WARNING: table '.$this->entity_code.' does not have lock_id, which is kinda BAD !!!');
else $this->addField('lock_id');
It's definitely not a full answer to your question, because I don't really get the point of this, but:
1) You should use public $table='conflicto' and not $entity_code because $entity_code is obsolete (but still usable for compatibility reasons)
2) I don't see any reason why you need to check if field exists in table before adding it to model. Is it possible that somebody simply delete field from database without notice and don't remove it from Model? If that's the case, then you need to change something in your project management and not create messy and veeeeeery slow code :)
Do you imagine how big stress that will generate to your database if for every initialization of this Model you'll query SHOW columns FROM table?!
Related
I have the following challenge with this class:
Public Class MyClass
Property Id As Integer
Property LastName as String
End Class
The corresponding data table in the database has as fields:
Id (int, not null)
Last-Name (nvarchar(80),null)
So I need to map MyClass.LastName to MyClasses.Last-Name and have a hell of a time...
When I write a custom Insert query it all works, but I would like to use the .Insert statement of one of the Dapper extensions packages.
I tried Dapper.Contrib, but this ignores mappings that I create using Dapper.FluentMap or using the built in method of Dapper itself using Dapper.SetTypeMap.
I tried Dapper.FastCrud, but I was unable to figure out how to configure mappings for it, though this API seems promising.
Anyone?
So, basically the problem here is that the property name and column name is different.
With Dapper, you can handle this by providing alias for column name in SQL query. I guess you have already tried this as you said "I write a custom Insert query" in your question. Other multiple ways to map column names with properties are discussed here.
With DapperExtensions, you can map the different column names with their respective properties something like below:
public sealed class MyClassMapper : ClassMapper<MyClass>
{
public MyClassMapper()
{
Table("MyTable");
Map(x => x.Id).Key(KeyType.WhatYouWant);
Map(x => x.LastName).Column("Last-Name");
AutoMap();
}
}
Code sample is with C#. You have to translate it to VB.NET.
With Dapper.Contrib, you can decorate the class with [Table] attribute to map the table name. Alternatively, you can use SqlMapperExtensions.TableNameMapper to map the tables. Please refer to this blog post or this and this and this SO posts for more details.
Apparently, there is no way to map the column name. This feature is planned for next major (2.x) version. Version 2.x is released; but the issue is still open. Looking at the release notes, feature is not yet added.
With Dapper.FastCrud, there are multiple ways for mapping. You can decorate the property with [Column] attribute.
Edit: This is not a problem with ignorance of basic programming (such as trying to dereference a null object reference).
Edit: Added the stack trace from EF within Linqpad.
Using EF6, I have a very simple query :
var menu = dbcontext.Tree.Where(t => t.Left > 2 && t.Right < 10).ToList();
This worked right up until it mysteriously stopped. dbcontext.Tree is a view in SQL Server 2012. Using Linqpad5, I get the results I expect using its built-in connection. Setting up an EF connection to my project, I get the NRE. Checking the SQL, I can copy and paste that into a SQL query window and get the proper results. I get an NRE without the Where call, also.
I've tried updating my model from database to refresh anything. I've tried removing the view from the model and updating. I've tried deleting the model entirely and recreating it. I've restarted Visual Studio AND my computer. I get the same NRE for the query. I don't know what else I can try, and this NRE makes no sense to me at all, given I get the results I expect using everything but EF. I'd chalk it up to a bug with EF if I didn't see it working previously.
Has anyone dealt with this? Searching online for this specific set of circumstances has produced nothing.
Stack Trace :
at System.Data.Entity.Core.EntityKey.AddHashValue(Int32 hashCode, Object keyValue)
at System.Data.Entity.Core.EntityKey.GetHashCode()
at System.Collections.Generic.GenericEqualityComparer`1.GetHashCode(T obj)
at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
at System.Data.Entity.Core.Objects.ObjectStateManager.TryGetEntityEntry(EntityKey key, EntityEntry& entry)
at System.Data.Entity.Core.Objects.ObjectStateManager.FindEntityEntry(EntityKey key)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.HandleEntityAppendOnly[TEntity](Func`2 constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator`1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnumerator.MoveNext()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
The problem is that your Model (Tree in your example above) has one ore more properties that are not nullable (and possibly also marked as not nullable in the mapping) but the corresponding column in the data store is nullable. This exception would only manifest itself as soon as there was a record being retrieved that had a null value for one of those column(s).
Model fix - When updating the model be sure to use Nullable<T> or ? for nullable value types and if you have mappings defined (either via attributes or in types that inherit EntityTypeConfiguration) also specify that the property is optional there.
Data store fix - Alternatively change the data store schema and data to align it with what is expected in the model.
I have a simple entity class in a WPF application that essentially looks like this:
public class Customer : MyBaseEntityClass
{
private IList<Order> _Orders;
public virtual IList<Order> Orders
{
get { return this._Orders; }
set {this._Orders = new ObservableCollection<Order>(value);}
}
}
I'm also using the Fluent automapper in an offline utility to create an NHibernate config file which is then loaded at runtime. This all works fine but there's an obvious performance hit due to the fact that I'm not passing the original collection back to NHibernate, so I'm trying to add a convention to get NHibernate to create the collection for me:
public class ObservableListConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
Type collectionType =
typeof(uNhAddIns.WPF.Collections.Types.ObservableListType<>)
.MakeGenericType(instance.ChildType);
instance.CollectionType(collectionType);
}
}
As you can see I'm using one of the uNhAddIns collections which I understand is supposed to provide support for both the convention and INotification changes, but for some reason doing this seems to break lazy-loading. If I load a custom record like this...
var result = this.Session.Get<Customer>(id);
...then the Orders field does get assigned an instance of type PersistentObservableGenericList but its EntityId and EntityName fields are null, and attempting to expand the orders results in the dreaded "illegal access to loading collection" message.
Can anyone tell me what I'm doing wrong and/or what I need to do to get this to work? Am I correct is assuming that the original proxy object (which normally contains the Customer ID needed to lazy-load the Orders member) is being replaced by the uNhAddIns collection item which isn't tracking the correct object?
UPDATE: I have created a test project demonstrating this issue, it doesn't reference the uNhAddins project directly but the collection classes have been added manually. It should be pretty straightforward how it works but basically it creates a database from the domain, adds a record with a child list and then tries to load it back into another session using the collection class as the implementation for the child list. An assert is thrown due to lazy-loading failing.
I FINALLY figured out the answer to this myself...the problem was due to my use of ObservableListType. In NHibernate semantics a list is an ordered collection of entities, if you want to use something for IList then you want an unordered collection i.e. a Bag.
The Eureka moment for me came after reading the answer to another StackOverflow question about this topic.
I have one UITableView class that can load a variety of data. All the actions on the data that is loaded will be the same regardless of what the data is, so I figured it would be best to keep it all in the same class. The problem I'm running into is that for one set of data, I need the UITableView to use the style UITableViewStyleGrouped, and the rest to use UITableViewStylePlain.
I was able to get this to work in prepareForSegue by using the following if statement:
if([whatToLoad isEqualToString:#"Sets"]){
[[segue destinationViewController] initWithStyle:UITableViewStyleGrouped];
}
else
[[segue destinationViewController] initWithStyle:UITableViewStylePlain];
But then Xcode gives me a warning on both of them of "Expression result unused". Is there something else I can do to achieve the same result without the warning? Or do I have to create a different class for the one set of data that needs a grouped table?
You can have multiple table view controllers in the storyboard file, and set the "Class" to the same UITableViewController subclass for each of them. So you can use the same controller code for each of them, but one has a "grouped" table view and the others have a "plain" table view.
Calling
[[segue destinationViewController] initWithStyle:...]
seems risky to me, because the destination view controller is an already allocated and initialized instance, and initXXX functions are generally allowed to return a different instance.
I have attached the Translate behavior to one of my models and I have some shortcomings regarding this:
1) If I don't save data in all fields passed as params when attaching the behavior to the model, $Model::find() method doesn't get the inserted rows.
public $actsAs = array(
'Translate' => array(
'title' => 'title_Translation',
'description' => 'description_Translation',
'description_long' => 'description_long_Translation'
)
);
Ex: if i pass to $Model::save() method only a value for 'title', the data is saved, even in the i18n table, but the $Model::find() doesn't get anything. I must pass data for all the fields.
Can I force it to retrieve those records ?
2) How can I get all the records in the admin side of the application (regardless of the language in which a record is saved) in order to list them so the user can alter it (edit data, save data in multiple languages)? Right now, I can only get the records that correspond to the current language (read from Configure or set explicitly)..
Thank you!
I kind of solved it, I copied the TranslateBehavior to app/Model/Behavior (just to avoid problems on future upgrades and keep the original one just in case) then I changed the _addJoin(...) method of the behavior, just changed the join type from INNER to LEFT on line 255 (I use cake 2.2.3).
Now if a record exist it is always retrieved, even if translated fields are missing.
Don't see any drawbacks besides the need to check if the translation field is empty.
OK, I might be a bit late, but anyway...
1) Cake uses an INNER JOIN when fetching a row and it's associated translations, so basically there's no easy way around this. You have to make sure you save every translatable field, every time - even if you just save it as blank. The only alternative would be to go hacking round the core to make it use a left join rather than an inner join - but don't do that.
2) The cookbook explains how to fetch all records here: http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#retrieve-all-translation-records-for-a-field
Now, probably most of the time you want to get just one translation, so you don't want to modify the definition of your $actsAs['Translate'] array in your model. So what I did, was set up a method in AppModel.php which modifies the $actsAs['Translate'] array on the fly:
/*
* See http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#using-the-bindtranslation-method
* This is for making it so we fetch all translations, as opposed to just that of the current locale.
* Used for eg. editing (multiple translations) via the admin interface.
*/
public function bindAllTranslations(){
$translatableFields = $this->actsAs['Translate'];
$keyValueFields = array();
foreach($translatableFields as $field){
$keyValueFields[$field] = $field.'Translation';
}
$this->bindTranslation($keyValueFields,false); // false means it will be changed for all future DB transactions in this page request - and won't be reset after the next transaction.
}
So, if it's an admin method (or any other situation you want all translations) you call that code before doing a find:
$this->MyModel->bindAllTranslations();
$this->MyModel->find('all');
Hope that helps!
Not exactly sure if it will help in your case, but you can also use
array to set locale before you call find()
$this->YourModel->locale = array("ENG", "GER", "JAP");
This way you will always get all records even if they don't have all possible translations.
Thanks a lot eleonzx, I'm having this problem since a decade, and with your simple answer I can now move forward ! So thanks again.
And maybe this code can help a lot of people :
in my AppController beforeFilter method I call _setLanguage
private function _setLanguage() {
if($this->Session->read('Config.language')){
$locale = $this->Session->read('Config.language');
$this->{$this->modelClass}->setLocale($locale);
}else{
$this->{$this->modelClass}->Behaviors->disable('Translate');
}
}
With the else condition I disable the Translate Behavior on the fly to get the original contents if there is no locale set in the session (I use basic links to switch between languages).