I have a field on Database called name but when I retrieve this I would that be called label.
In raw SQL I can do that like this:
SELECT name AS label FROM ...
Theres a "quick way" to do this on find without need to deal with entity, virtual fields etc...
I did it using virtual properties like this on Entity:
protected $_virtual = ['label'];
protected function _getlabel()
{
return $this->_properties['name'];
}
A potential gotcha could be... I'm was using _serialize to return the result and it was not working because you have to expose the virtual properties using $_virtual.
Related
Contrived example, but let's say I have a these entities:
public class Root
{
public virtual Customer Customer { get; set; }
}
public class Customer
{
public virtual CustomerData Data { get; set; }
}
public class CustomerData
{
public virtual string FooName { get; set; }
}
Now, let's say I want to create a filter for Root based on the value of FooName. Intuitively, I tried this in my FooMap class. Using Fluent mappings.
ApplyFilter("FooNameFilter", "Customer.Data.FooName in (:argument)");
This doesn't work. A SqlClient.SqlException is thrown stating The multi-part identifier "Customer.Data.FooName" could not be bound.
Is there a way to make filters work this way, or am I forced to move that logic into all Query<Root>()s instead?
What could be working, is to move the filter to the CustomerData object if possible, or to create "more sophisticated SQL condition" applied on the Customer mapping. But it is about pure SQL, no references. How do the filters work?
The filters are the same as the where clause, but could be adjusted in a runtime. The extract from documentation 18.1. NHibernate filters
NHibernate adds the ability to pre-define filter criteria and attach
those filters at both a class and a collection level. A filter
criteria is the ability to define a restriction clause very similiar
to the existing "where" attribute available on the class and various
collection elements. Except these filter conditions can be
parameterized. The application can then make the decision at runtime
whether given filters should be enabled and what their parameter
values should be. Filters can be used like database views, but
parameterized inside the application.
The definition of the where:
where (optional) specify an arbitrary SQL WHERE condition to be used when retrieving objects of this class
Other words, these settings act as "add on" to our mapping. They are extending it (both where and filter) with more SQL balast. The filter could be shared among many mappings and applied to all queries inside one session, but it must target the column:
condition=":myFilterParam = MY_FILTERED_COLUMN"
We have an entity named Organization that we use the UniqueConstraints-bundle on. We have a property named NetName that is a UniqueConstraint and an automaticly generated Id.
Since this is unneccesary we want to use the NetName-property as Id instead. So that we don't need UniqueConstraints to know that it is unique and also get the benefit from being able to use Load when we have the NetName.
We needed to clean up our netname a bit before using it as an Id so we created a new temporary-property called TempUniqueNetName that now holds the value of:
"organizations/"+ CleanupId(this.NetName)
So we are now ready to simply move that value to our Id. But we can't get it to work. Our problem is that with the PatchRequest below we end up with a new property named Id in the database but the acctual Id still has the same value (see screenshot). Is there a better (correct) way to change the value of an Id?
The Entity:
class Organization {
public string Id { get; set; }
[UniqueConstraint]
public string NetName { get; set; }
public string TempUniqueNetName{ get; set; }
}
We want to do something like this:
_documentStore.DatabaseCommands.UpdateByIndex(typeof(Organizations).Name,
new IndexQuery(),
new[]
{
new PatchRequest()
{
Type = PatchCommandType.Rename,
Name = "TempUniqueNetName",
Value = new RavenJValue("Id")
}
});
I don't think you can change the document key via patching. It's not actually stored with the document or the metadata - it's copied into the #id metadata on load to give you the illusion that it's there, and the Raven Client copies it again into your own identity property in the document. But really, it's a separate value in the underlying esent document store. Raven would have to know specifically how to handle this and fake it for you.
You could manually copy the doc from the old id to the new one and delete the old, but that could be time consuming.
There isn't a great answer for renaming a document key right now. There really should be a DatabaseCommand for rekeying a single document, and separate PatchCommandType to rekey when patching. Perhaps this will be added to raven in the future.
You can check implemtation of PUT-DELETE usage for updating IDs in my github repo.
It should look something like this:
store.DatabaseCommands.Put(updatedKey, null, document.DataAsJson, newMetadata);
store.DatabaseCommands.Delete(oldKey, null);
https://github.com/Sevsoad/SagaUpdater/
Also here is some Raven documentation:
https://ravendb.net/docs/article-page/3.0/csharp/client-api/commands/documents/put
In my application, i have fields that are common to all tables, like create date, update date etc. To assign these values i'm using beforeValidate callback. Now, this callback is same for all models.
To avoid code duplication, i want to create a base model class.
But, when I tried to create a base model, yii thrown error saying table cannot be found in database, which is true since I dont have any table for this base model.
Is there any way I can create a base model class.
Yes, if you work with dynamic DB structure or have other reasons to work with Yii ActiveRecord without creating classes for each table in DB, you may use smartActiveRecord yii extension
I separated it few minuts ago from my other extension -- AR behavior that adds versioning to any model (it copies all data on insert & update to special table (and create it if it's absent), that have a same structure as source table + "revision field" and primary key extended by this field.
Look at SmartAR.php source, there is example of usage in comments.
Take a look at CTimeStampBehavior.
Incase that doesn't help you, you can just write a behavior class yourself.
Hope this helps.
Edit:Assuming you are using ActiveRecords.
If you want to create a new base model, you can do this:
abstract class MyBaseARClass extends CActiveRecord{
protected function beforeValidate(){
if(parent::beforeValidate()){
// assign your fields
return true;
}
else return false;
}
}
Have you created a base table? Thinking about the Yii framework it may be easier to have a relationship between a model and the base model.
In your case, you need to override
public static function model($className=__CLASS__)
{
return parent::model($className);
}
in every child class so Yii would know which table to use for your model. Otherwise it will try and use base class as table name.
I.e.
class User extends BaseActiveRecord {
public static function model($className=__CLASS__)
{
return parent::model($className);
}
}
I started using NHibernate today, but I cannot figure out how I setup a simple relation between two tables. I don't really know what it's called, it could be one-to-many or foreign key relation (I'm not that into database design and the terms used), but here's a very simple example.
I have a table Product with attributes Id (PK), ProductName and CategoryId. Then I have a table Categories with attributes Id (PK) and CategoryName.
I created these classes:
public class Product
{
public virtual int Id { get; set; }
public virtual string ProductName { get; set; }
public virtual int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual string CategoryName
{
get { return this.Category == null ? String.Empty : this.Category.CategoryName; }
}
}
public class Category
{
public virtual int Id { get; set; }
public virtual string CategoryName { get; set; }
}
In other words, I simply want the Product to store to which category it belongs (via the CategoryId attribute which points to an Id in the Categories table). I don't need the Category class to hold a list of related Products, if that makes it any simpler.
To make it even more clear what I'm after, this is the SQL that I'm expecting:
SELECT Products.*, Categories.*
FROM Products INNER JOIN Categories ON Products.CategoryId = Categories.Id
at least that's what I think it should be (again, I'm not that good at database design or queries).
I can't figure out which kind of mapping I need for this. I suppose I need to map it in the Product.hbm.xml file. But do I map the CategoryId as well? And how do I map the Category property?
It seems like I would need a 'one-to-many' relation since I have ONE category per product (or is this reasoning backward?) but it seems like there is no one-to-many mapping...
Thanks for any help!
Addition:
I tried to add the many-to-one relation in the Person mapping, but I keep getting an exception saying "Creating proxy failed", and in the inner exception "Ambiguous match found".
I should maybe mention I am using an old version of NHibernate (1.2 I think) because that is the only one I got running with MS Access due to it not finding the JetDriver in newer versions.
I've put the mapping files, classes, and code where the error occurs in screenshots because I can't figure out how to post XML code here... It keeps reading it as html tags and skipping half of it. Anyway.
The mappings:
http://www.nickthissen.nl/Images/tmp7B5A.png
The classes:
http://www.nickthissen.nl/Images/tmpF809.png
The loading code where the error occurs:
http://www.nickthissen.nl/Images/tmp46B6.png
(As I said, the inner exception says "Ambiguous match found".
(Product in my example has been replaced by Person)
The Person and Category classes inherit Entity which is an abstract base class and defines the Id, Deleted, CreatedTime and UpdatedTime properties.
The code where the error occurs is in a generic 'manager' class (type parameter TEntity which must inherit Entity). It is simply supposed to load all entities with the Deleted attribute false. In this case, TEntity is 'Person'.
It works fine if I leave out the many-to-one Category mapping in the Person mapping, but then obviously the Category property is always null.
Oh yeah, sorry about the mix between C# and VB, the C# code is in a generic framework I use for multiple projects while the VB part is the actual implementation of that framework on my website and I just happened to use VB for that.
Help? Thanks!
In your Product class only needs to contain a Category object, you don't need a CategoryId property. Then in your Product mapping you need to have this entry
<many-to-one name="Category" column="CategoryId" />
UPDATE:
Your mappings appear to be missing the fully qualified name of the mapped class in the tag. See http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-class
UPDATE 2:
See if this helps you NHibernate 1.2 in a .NET 4.0 solution
The 'Ambiguous match found' exception was caused by the project targeting .NET Framework 4, which does not seem to be compatible with NHibernate 1.2.1. I switched to 3.5 and that seems to solve that particular issue.
Now on to the next. As you can see, the Person class has a CategoryName property that should return the name of the current Category object, or an empty string if the category happens to be null. This is so I can databind a collection of Person objects to a grid, specifying 'CategoryName' as a property to bind a column to.
Apparently this does not work with NHibernate. Whenever I try to databind my collection of persons, I get this exception:
"Property accessor 'CategoryName' on object 'NHibernateWebTest.Database.Person' threw the following exception:'Could not initialize proxy - the owning Session was closed.'"
This occurs on the 'DataBind' method call in this code:
public virtual void LoadGrid()
{
if (this.Grid == null) return;
this.Grid.DataSource = this.Manager.Load();
this.Grid.DataBind();
}
(This is an ASP.NET project and 'Grid' is a GridView)
'this.Manager' returns an existing instance of NHibernateEntityManager, and I've already shown its Load method before, it contains this:
public virtual EntityCollection Load()
{
using (ISession session = this.GetSession())
{
var entities = session
.CreateCriteria(typeof (TEntity))
.Add(Expression.Eq("Deleted", false))
.List();
return new EntityCollection(entities);
}
}
(THere's some generic type parameters in there but this website seems to hide them (due to the html like tags I guess)... Sorry about that).
This may have something to do with NHibernate itself, as I said I'm completely new to this. When I call my Load method I would expect it to return an EntityCollection(Of Person) with all its properties already set. It seems I have to keep the ISession open while I am databinding for some reason..? That seems a little strange...
Can I get around this? Can I make my Load method simply return a collection of persons already fully loaded, so that I can access CategoryName whenever I want?
Wait... Is this lazy loading perhaps?
I have a Project model that has a property of type IProjectWorker, this could either be a single User or a Team. In Castle ActiveRecord it's defined like this:
[Any(typeof(int), MetaType = typeof(string), TypeColumn = "WorkerType", IdColumn = "WorkerID", Cascade = CascadeEnum.None)]
[Any.MetaValue("USER", typeof(User))]
[Any.MetaValue("TEAM", typeof(Team))]
public IProjectWorker Worker { get; set; }
Now I need to be able to search for projects where the worker's name contains some text. My initial reaction was something like this:
query
.CreateAlias("Worker", "Worker")
.Add(Restrictions.InsensitiveLike("Worker.WorkerName", SearchText, MatchMode.Anywhere));
But this gives me an error-- "any types do not have a unique referenced persister". This makes sense, it doesn't know how to handle joining to the two different tables for the search.
Can I make two different aliases for each table and do a Restrictions.Or() across them? I tried it, but couldn't quite get it right. Or is there some other way to do this using criteria that I'm missing? Or am I going to have to use HQL instead?