More on my topic of ensuring all references in your class are strongly typed.
NOTE: Please see comments below, in most cases (if not all) you don't have to specify the optional association arguments, will verify when you need to use these parameters and place that on the Wiki. For those who do specify the name the post still provides some ideas but ultimately doesn't really apply to every day use. my thx to Noxe for bringing this to my attention.
How many times have you received a “PropertyTypeMismatchException” because you didn’t name your association the same between two classes?
It is easy to put a type in and cause some frustration while trying to find which side.
How can you handle this easier? Easy just declare Constant on one side of your association and reference that for your AssociationAttribute.
I am defining my Association Constant on the one side of the relationship, technically another option could be to create a Module/Static class called “Associations” and place your constants in there, but personally I prefer them to be apart of the objects the association is based on.
Here is an example of a “Strongly Typed” XPO class with Association, the only place strings are used is in the Constant declaration and within the FieldsClass itself. This makes it almost kiddy safe as you will receive compile errors if you start refactoring and not changing the bits you should.
Hope it helps
Public Class Company
Inherits XPObject
Private fTradingName As String
Public Property TradingName As String
Get
Return fTradingName
End Get
Set(ByVal Value As String)
SetPropertyValue(Fields.TradingName.PropertyName, fTradingName, Value)
End Set
End Property
Friend Const EmployeeAssociation As String = "CompanyEmployees"
_
Public ReadOnly Property Employees() As XPCollection(Of Employee)
Get
Return GetCollection(Of Employee)(Fields.Employees.PropertyName)
End Get
End Property
Private Shared fFields As FieldsClass
Public Shared Shadows ReadOnly Property Fields() As FieldsClass
Get
If ReferenceEquals(fFields, Nothing) Then
fFields = New FieldsClass()
End If
Return fFields
End Get
End Property
'Created/Updated: PC-ALF\PC-ALF\Michael 6/10/2010 7:07 AM
Public Shadows Class FieldsClass
Inherits DevExpress.Xpo.XPObject.FieldsClass
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal propertyName As String)
MyBase.New(propertyName)
End Sub
Public ReadOnly Property TradingName() As DevExpress.Data.Filtering.OperandProperty
Get
Return New DevExpress.Data.Filtering.OperandProperty(GetNestedName("TradingName"))
End Get
End Property
Public ReadOnly Property Employees() As DevExpress.Data.Filtering.OperandProperty
Get
Return New DevExpress.Data.Filtering.OperandProperty(GetNestedName("Employees"))
End Get
End Property
End Class
End Class
Public Class Employee
Inherits XPObject
Private fCompany As Company
Public Property Company As Company
Get
Return fCompany
End Get
Set(ByVal Value As Company)
SetPropertyValue(Fields.Company.PropertyName, fCompany, Value)
End Set
End Property
Private fFullname As String
Public Property Fullname As String
Get
Return fFullname
End Get
Set(ByVal Value As String)
SetPropertyValue(Fields.Fullname.PropertyName, fFullname, Value)
End Set
End Property
Private Shared fFields As FieldsClass
Public Shared Shadows ReadOnly Property Fields() As FieldsClass
Get
If ReferenceEquals(fFields, Nothing) Then
fFields = New FieldsClass()
End If
Return fFields
End Get
End Property
'Created/Updated: PC-ALF\PC-ALF\Michael 6/10/2010 7:07 AM
Public Shadows Class FieldsClass
Inherits DevExpress.Xpo.XPObject.FieldsClass
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal propertyName As String)
MyBase.New(propertyName)
End Sub
Public ReadOnly Property Company() As XpoSqlIdTest.Company.FieldsClass
Get
Return New XpoSqlIdTest.Company.FieldsClass(GetNestedName("Company"))
End Get
End Property
Public ReadOnly Property Fullname() As DevExpress.Data.Filtering.OperandProperty
Get
Return New DevExpress.Data.Filtering.OperandProperty(GetNestedName("Fullname"))
End Get
End Property
End Class
End Class
public class Company : XPObject
{
private string _TradingName;
public string TradingName
{
get
{
return _TradingName;
}
set
{
SetPropertyValue(Fields.TradingName.PropertyName, ref _TradingName, value);
}
}
internal const string EmployeeAssociation = "CompanyEmployee";
[Association(Company.EmployeeAssociation, typeof(Employee))]
public XPCollection Employees
{
get
{
return GetCollection(Fields.Employees.PropertyName);
}
}
private static FieldsClass fFields;
public new static FieldsClass Fields
{
get
{
if (ReferenceEquals(fFields, null))
fFields = new FieldsClass();
return fFields;
}
}
//Created/Updated: PC-ALF\PC-ALF\Michael 6/10/2010 7:14 AM
public new class FieldsClass : DevExpress.Xpo.XPObject.FieldsClass
{
public FieldsClass()
: base()
{
}
public FieldsClass(string propertyName)
: base(propertyName)
{
}
public DevExpress.Data.Filtering.OperandProperty TradingName
{
get
{
return new DevExpress.Data.Filtering.OperandProperty(GetNestedName("TradingName"));
}
}
public DevExpress.Data.Filtering.OperandProperty Employees
{
get
{
return new DevExpress.Data.Filtering.OperandProperty(GetNestedName("Employees"));
}
}
}
}
public class Employee : XPObject
{
private Company company;
[Association(Company.EmployeeAssociation,typeof(Company))]
public Company Company
{
get
{
return company;
}
set
{
SetPropertyValue(Fields.Company.PropertyName, ref company, value);
}
}
private string fullname;
public string Fullname
{
get
{
return fullname;
}
set
{
SetPropertyValue(Fields.Fullname.PropertyName, ref fullname, value);
}
}
private static FieldsClass fFields;
public new static FieldsClass Fields
{
get
{
if (ReferenceEquals(fFields, null))
fFields = new FieldsClass();
return fFields;
}
}
//Created/Updated: PC-ALF\PC-ALF\Michael 6/10/2010 7:14 AM
public new class FieldsClass : DevExpress.Xpo.XPObject.FieldsClass
{
public FieldsClass()
: base()
{
}
public FieldsClass(string propertyName)
: base(propertyName)
{
}
public CurlyBracketTest.Company.FieldsClass Company
{
get
{
return new CurlyBracketTest.Company.FieldsClass(GetNestedName("Company"));
}
}
public DevExpress.Data.Filtering.OperandProperty Fullname
{
get
{
return new DevExpress.Data.Filtering.OperandProperty(GetNestedName("Fullname"));
}
}
}
}
Add comment
Comments
Yes that is true, however I believe there is some performance issues with that approach. I haven't researched it heavily nor done any testing however same as SetPropertyValu e the more you use the "generic" overloads the more Reflection is used, as XPO will have to look through the ClassInfo to find out information.
An another example with the Association with the optional Type parameter, you don't have to specify it however if you don't XPO has to use Reflection on the property to get the neccessary details.
To my knowledge these properties can be hit very often in certain operations so the more efficient they are the better.
But again I won't declare that as fact, it is just my understanding of XPO. I would appreciate if anyone has any other information on the topic to let us know.
i dont think reflection is used here - see MetaData.cs - GetAssociatedMe mber method. It always loops through all members there. I dont know if its best practive what i am doing - i do it to get rid of the association strings :)
I made the assumption that like SetPropertyValu e the more information provided reduces Reflection usage, however after your comments and looking at the source I understand now that regardless of the information XPO will have to use Reflection to "verify" the relationship anyway so regardless what you do the end result is the same
So the next question is what is the use case for specifying an Association name... need to look at the code again to see how XPO handles if you have two associations of same types on the same classes, ie. Client.SalesBilled -> Sales.BillTo and Client.Sales -> Sales.Site
One would assume XPO will build an unique association key using the names on both classes, but will have to look into that.
The next thing is specifying the Type for the association, where is that required/recommended? again will have to look at the code.
Might pop DX a question on the support center to get some more info on the Best Practice/Use Cases for those optional params.
Thanks for the heads up, this is something I love about the community, we are all constantly learning every day.
Found that the type argument in the association is only required if you are not using the generic XPCollection return type, if you are using XPCollection you need to tell XPO what the other side of the relation is, makes sense really but not very clear.
I still haven't found out the exact scenario of when to specify the Name property, I would assume the Best Practice would be to define it, that way if you do add other associations on your object you won't accidentally end up with conflicts. But it isn't a major issue.
Just thought I would let ya know.
you are right - community is a great thing - i also learn every day :)
You are right about multiple associations - in this case i think xpo cannot resolve the correct associations. would be interessting whats the best practice here...
Just had a look and it explains it much better
documentation.devexpress.com/#XPO/CustomDocument2041
RSS feed for comments to this post.