This thread looks to be a little on the old side and therefore may no longer be relevant. Please see if there is a newer thread on the subject and ensure you're using the most recent build of any software if your question regards a particular product.
This thread has been locked and is no longer accepting new posts, if you have a question regarding this topic please email us at support@mindscape.co.nz
|
Hi, This problem has me perplexed, I've tried numerous things but nothing seems to help. I have two entities mapped to my oracle db, Formula and Formulation. Formula has two composite keys and Formulations has three composite keys. They're setup in lightspeed as valueobjects and seem to load OKAY independed of each other. But once I define a one-to-many associatoni (formula-formulations) I can no longer load a Fromulation. I am receiving an "Index was outside the bounds of the array." error. Please note the exception is raised even though eager loading is off and the object never gets loaded. However, loading a Formula from the db works even though trying to resolves it's associations to Formulations raises a different exception: "Object of type 'System.Decimal' cannot be converted to type 'System.String'." Properties of both entities are of matching type (string & int32). However in the db, the Int32 field from Formulation maps to a 'Number' field of 10 precision and the int32 field from Formula maps to a Number field (no precision set). Unfortunately it's a legacy db so I cannot modify it, but if this is the cause is there a work around in Lightspeed? Why am seeing two differeng types of errors even though they're of the same type - int32.
Thanks, SR
|
|
|
Could you post the code for the two entities in question? |
|
|
Please see the attached code. Thanks in advance. |
|
|
Looking at your classes it looks to me like the foreign key and primary key columns overlap: specifically in Formulation the RECIPE_CODE and sys_version_number columns appear twice, once as part of the FormulaId foreign key and once as part of the FormulationId primary key. Is that right? LightSpeed doesn't allow a column to be mapped to more than one field (and the weird casting errors you see are symptomatic of duplicate mappings). If this is the case, you will need to use a custom association resolver. This is currently the only way in LightSpeed to handle a composite FK that includes parts of the PK. Unfortunately this is pretty fiddly and introduces some limitations particularly around cascade deletion. You can find an example in this thread: http://www.mindscapehq.com/forums/Thread.aspx?ThreadID=2653&PageIndex=2 If I've misunderstood the column layout, let me know. |
|
|
Thanks for the response, Ivan. The columns are not mapped twice, it my appear so because the column names in both db table are Identical but each column is mapped to exactly one field for each entity. I think to add to the confusion columns with the same name are also the primary/foreign key to one another. Hope this clarifies..
|
|
|
Let me rephrase the question. Am I right in thinking that in the database RECIPE_LINES table, the primary key columns are also the foreign keys to the UN_VW_NEWEST_RECIPES table? If that's the case, then you will need to use a custom association resolver because otherwise you will end up with duplicate column mappings. The reason for this is that (in the absence of a custom resolver) the primary key columns are represented in LightSpeed by the Id property (of type FormulationId), and the foreign key columns are represented by a FormulaId property (of type FormulaId). Therefore, if the primary key columns are the foreign key columns, then they will end up mapped to both the Formulation.Id and the Formulation.FormulaId. This can be a bit difficult to see in the designer so let me show you the generated code and hopefully it will become clearer why I think the columns are mapped twice. Here is the foreign key declaration in the Formulation class: [ValueObjectColumn("Version", "sys_version_number")] Those attributes map RECIPE_LINES.sys_version_number to Formulation._formulaId.Version and RECIPE_LINES.RECIPE_CODE to Formulation._formulaId.RecipeCode. Which is of course what you want. But here are the fields of the FormulationId struct: [Column("RECIPE_CODE")] When used in the Formulation.Id identity, these attributes map RECIPE_LINES.RECIPE_CODE to Formulation.Id.RecipeCode and RECIPE_LINES.sys_version_number to Formulation.Id.Version. So you can see that, for example, RECIPE_LINES.RECIPE_CODE is mapped to both Formulation._formulaId.RecipeCode and Formulation.Id.RecipeCode. And this causes big problems for LightSpeed. The confusion probably arises because the designer doesn't show the individual fields of the Formulation._formulaId foreign key, so only the mappings to the composite primary key are visible on the designer surface, and it therefore looks as though the RECIPE_LINES columns are mapped into Formulation only once. However, you can see the composite FK column mappings set up if you click the association arrow and look at the properties grid, and I think you will find that they duplicate the composite PK column mappings. I hope that makes a bit more sense now -- sorry if I skipped over some of the details first time around! |
|
|
Thanks, Ivan. Yes, it does make sense. In my frustration I overlooked the minor, yet critical, detail that mapping a relationship also maps the fields and will cuase duplication. I will try the custom resolver approach (yea, wish me luck) but since this project is mostly only for a data migration process if all else fails I will use another field (like timestamp) to act as my primary key. Thanks for the help. |
|
|
Thanks, Ivan. I attempted to implement a custom association resolver (based on the example from http://www.mindscapehq.com/forums/Post.aspx?ThreadID=2653&PostID=11412) but I'm getting a 'circular association' error. I'm not even sure if my custom resolver is firing or not but here's the (partial) code: public partial class Formulation : Entity<FormulationId>{#region Customer Association Resolver[Transient]private FormulationId _pendingId;public static Formulation New(string recipeCode, int version, string partCode){return new Formulation { _pendingId = new FormulationId(recipeCode, version, partCode) };}protected override object GeneratedId(){return _pendingId;}#endregion#region Relationships[ReverseAssociation("Formulations")][AssociationResolver(typeof(ParentFormulaResolver))]private readonly EntityHolder<Formula> _formula = new EntityHolder<Formula>();private class ParentFormulaResolver : IAssociationResolver{public QueryExpression GetQueryExpression(Entity sourceEntity){Formulation formulation = (Formulation)sourceEntity;return Entity.Attribute("Id") == new FormulationId(formulation.Id.RecipeCode, formulation.Id.Version, formulation.Id.PartCode);}public void SetForeignKey(Entity childEntity, object parentId){}}#endregion#region Propertiespublic Formula Formula{get { return Get(_formula); }set { Set(_formula, value); }}#endregion}public partial class Formula : Entity<FormulaId>{#region Customer Association Resolver[Transient]private FormulaId _pendingId;public static Formula New(string recipeCode, int version){return new Formula { _pendingId = new FormulaId(recipeCode, version) };}protected override object GeneratedId(){return _pendingId;}#endregion#region Relationships[ReverseAssociation("Formula")][AssociationResolver(typeof(ChildFormulationsResolver))]private readonly EntityCollection<Formulation> _formulations = new EntityCollection<Formulation>();private class ChildFormulationsResolver : IAssociationResolver{public QueryExpression GetQueryExpression(Entity sourceEntity){Formula formula = (Formula)sourceEntity;return Entity.Attribute("Id") == new FormulaId(formula.Id.RecipeCode, formula.Id.Version);}public void SetForeignKey(Entity childEntity, object parentId){}}#endregion#region Propertiespublic EntityCollection<Formulation> Formulations{get { return Get(_formulations); }}#endregion} The structs used to define teh "Ids" are attached in the previous code. Not entirely sure if this is how to approach the custom resolver implementation or what I am missing here. As always, any help will be greatly appreciated. Thanks. |
|
|
Thanks, Ivan. I attempted to implement a custom association resolver (based on the example from http://www.mindscapehq.com/forums/Post.aspx?ThreadID=2653&PostID=11412) but I'm getting a 'circular association' error. I'm not even sure if my custom resolver is firing or not but here's the (partial) code: public partial class Formulation : Entity<FormulationId>{#region Customer Association Resolver[Transient] The structs used to define teh "Ids" are attached in the previous code.Not entirely sure if this is how to approach the custom resolver implementation or what I am missing here. As always, any help will be greatly appreciated. Thanks. |
|
|
Your ChildFormulationsResolver looks wrong. This needs to query for a collection, and a query by Id will only ever return one item. You probably want something like: Entity.Attribute("Id.RecipeCode") == formula.Id.RecipeCode so that it is looking for all Formulations whose RecipeCode and Version match. Regarding the circular association, this looks like there is a bug where LightSpeed expects to find a field with the foreign key naming convention (_formulaId) even though that FK field would never be used because the resolver is used instead. Add the following field to the Formulation entity: #pragma warning disable 169 (The #pragma simply suppresses the compiler 'field is never used' warning.) We'll try to get this fixed for you but in the meantime this appears to work around the issue. |
|
|
Thanks for the response. I had tried to change the _pendingId field to _formulaId since that's the default naming it looks for but that had not helped. Use the '#pragma' approach seems to work, however, it's still trying to look for a column name 'FORMULAID' when loading Formulations from the database: ORA-06550: line 8, column 16: So I don't think my custom resolver is still firing. I tried to add the [Transient] attribute to _formulaId but that causes teh circular reference error again. Please help.. Thanks. |
|
|
Okay, I've tracked down the underlying bug and implemented a candidate fix, so you can remove the #pragma/_formulaId kludge. The fix will be in the next nightly build, available from about 1200 GMT. It's had only limited testing so be warned we may have to iterate this a couple of times -- as always, please let us know and we will try to get you fixes promptly. During testing I did find that I needed to update the resolvers. The 'ParentFormulaResolver' (on the Formulations.Formula association) needs to look like this: Formulation formulation = (Formulation)sourceEntity; and the 'ChildFormulaResolver' (on the Formula.Formulations association) like this: Formula formula = (Formula)sourceEntity; The thing to remember is that the Entity.Attribute is applying to the *target* type of the association -- the kind of entity that is going to be loaded. For example, in Formulations.Formula, we need to load a Formula, so Entity.Attribute("Id") will be a FormulaId not a FormulationId. Also note that if you want to insert/update entities then you will need to implement SetForeignKey on the ParentFormulaResolver. |
|
|
Ivan thank you so much for such fast response times. This is now working. Just a minor fine tuning.. I added another association to Formula to an entity named Description (also has cross cutting keys) but it doesn't work as 1-to-1 association (EntintyHolder defined on both ends of the relationship) but it works fine when I make it 1-to-N. I can work around it for now as a 1-to-N but perhaps in the future we can have a tweaked version of the code. Here's a copy of my code that fails and returns a circular reference error. Thanks again for all the help! |
|
|
I also noted when I add '[EagerLoad]' attribute to the relationships I get an error: An unhandled exception of type 'System.StackOverflowException' occurred in Mindscape.LightSpeed.DLL System.StackOverflowException {Cannot evaluate expression because the current thread is in a stack overflow state.} |
|
|
Ah, I didn't realise you had one-to-one associations in there. At the moment we don't support one-to-one associations with custom resolvers and no separate foreign key field -- I can probably get them added for you but it will be a little bit fiddly (as if it wasn't fiddly enough already!). How important is one-to-one support for you? EagerLoad doesn't work with custom association resolvers (because a CAR resolves a specific instance of the association, where you have a materialised entity and need to traverse its association -- building an eager load cascade query is a LOT more complicated!). We'll try to fix the StackOverflowException though -- thanks for the heads up. |
|
|
Thanks Ivan. As of right now neither are a big issue. This particular project is being used for a mass migration of db from a legacy db to a new system but in the future might be helpful. As long as I am able to load the child entities I dont care if it returns a collection of object that's always going to include just one object. I can always use [0] index to get to it. As for the [EagerLoad] issue, I do a count .Count() when I load the objects which forces the child entities to be loaded. This logic however is in my application layer, I'd like to move it to my business layer if you can get the stack over flow error to stop. Thanks. |
|
|
The StackOverflowException should be gone in the next nightly build. |
|
|
Sounds good! But, do you realise ability to specify AssociationResolverAttribute in designer? Thanks. |
|
|
Dtyger, I had the same issue where any custom code would be over written by the designer, don't know if there's a way to tell the designer that piece of code was not generated by it and not to mess with it! I worked around the problem by creating a seperate partial class defined in another .cs file and placing all my custom/hand written code in there. It's nice to have that kind of abstraction if you're going to be writing a lot of custom logic. |
|
|
You can specify custom resolvers in the designer via the Collection Resolver and Backreference Resolver settings on the one-to-many association arrow. The former is equivalent to applying AssociationResolverAttribute to the EntityCollection, the latter to the EntityHolder. |
|
|
[quote user="SR8"]I worked around the problem by creating a seperate partial class defined in another .cs file and placing all my custom/hand written code in there. It's nice to have that kind of abstraction if you're going to be writing a lot of custom logic.[/quote] I agree with you, but it's hard to place an attribute for field in separate file.... |
|
|
[quote user="ivan"]You can specify custom resolvers in the designer via the Collection Resolver and Backreference Resolver settings on the one-to-many association arrow.[/quote] A-ha, but what about one-to-one association? |
|
|
We don't currently support custom resolvers on one-to-one associations. This is on the wish list, but for now you will have to model the association as a one-to-many association, and use application logic to prevent more than one item being added to the collection. A possible way to do this is to set the association's Generation property to FieldOnly, then write the property accessors in the partial class, only instead of the EntityCollection accessor write something like this: public MyEntity MyEntity { private EntityCollection MyEntities { I will try to bump one-to-one custom association support up the priority list for you but I can't make any promises -- sorry! |
|
|
I have encountered strange problem. The next code runs perfect in clean project (created specialy for test), but throws error when executed from working project! Error is: Circular associations are not supported by LightSpeed: [AcpClient.Sborg and Sborg.Clients]Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
[Serializable] [System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")] [System.ComponentModel.DataObject] [Table("SB_ORGU_V", Schema="BANK")] public partial class Sborg : Entity<SborgId> { #region Fields [ValidateLength(0, 70)] private string _name; #endregion #region Field attribute and view names /// <summary>Identifies the Name entity attribute.</summary> public const string NameField = "Name"; #endregion #region Relationships [AssociationResolver(typeof(ChildClientResolver))] [ReverseAssociation("Sborg")] private readonly EntityCollection<AcpClient> _clients = new EntityCollection<AcpClient>(); #endregion #region Properties [System.Diagnostics.DebuggerNonUserCode] public EntityCollection<AcpClient> Clients { get { return Get(_clients); } } [System.Diagnostics.DebuggerNonUserCode] public string Name { get { return Get(ref _name, "Name"); } set { Set(ref _name, value, "Name"); } } #endregion } [Serializable] [System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")] public partial struct SborgId { public SborgId( long subj , int fil ) { _subj = subj; _fil = fil; } #region Fields private readonly long _subj; private readonly int _fil; #endregion #region Properties [System.Diagnostics.DebuggerNonUserCode] public long Subj { get { return _subj; } } [System.Diagnostics.DebuggerNonUserCode] public int Fil { get { return _fil; } } #endregion #region Dictionary support public override int GetHashCode() { int hashCode = 0; hashCode = 19 * hashCode + _subj.GetHashCode(); hashCode = 19 * hashCode + _fil.GetHashCode(); return hashCode; } #endregion } [Serializable] [System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")] [System.ComponentModel.DataObject] [Table("ACP_CLIENT")] public partial class AcpClient : Entity<int> { #region Fields [ValidateUnique] private System.Nullable<long> _gsubj; private System.Nullable<long> _subj; private System.Nullable<int> _fil; [Transient] [ValueObject] private SborgId _sborgId; #endregion #region Field attribute and view names /// <summary>Identifies the Gsubj entity attribute.</summary> public const string GsubjField = "Gsubj"; /// <summary>Identifies the Subj entity attribute.</summary> public const string SubjField = "Subj"; /// <summary>Identifies the Fil entity attribute.</summary> public const string FilField = "Fil"; #endregion #region Relationships [AssociationResolver(typeof(ParentSborgResolver))] [ReverseAssociation("Clients")] private readonly EntityHolder<Sborg> _sborg = new EntityHolder<Sborg>(); #endregion #region Properties [System.Diagnostics.DebuggerNonUserCode] public Sborg Sborg { get { return Get(_sborg); } set { Set(_sborg, value); } } [System.Diagnostics.DebuggerNonUserCode] public System.Nullable<long> Gsubj { get { return Get(ref _gsubj, "Gsubj"); } set { Set(ref _gsubj, value, "Gsubj"); } } [System.Diagnostics.DebuggerNonUserCode] public System.Nullable<long> Subj { get { return Get(ref _subj, "Subj"); } set { Set(ref _subj, value, "Subj"); } } [System.Diagnostics.DebuggerNonUserCode] public System.Nullable<int> Fil { get { return Get(ref _fil, "Fil"); } set { Set(ref _fil, value, "Fil"); } } [System.Diagnostics.DebuggerNonUserCode] public SborgId SborgId { get { return Get(ref _sborgId); } set { Set(ref _sborgId, value, "SborgId"); } } #endregion } /// <summary> /// Provides a strong-typed unit of work for working with the ModelTest model. /// </summary> [System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")] public partial class ModelTestUnitOfWork : Mindscape.LightSpeed.UnitOfWork { public System.Linq.IQueryable<Sborg> Sborgs { get { return this.Query<Sborg>(); } } public System.Linq.IQueryable<AcpClient> AcpClients { get { return this.Query<AcpClient>(); } } } |
|
|
Your clean and "real" projects may be referencing different versions of the LightSpeed DLL, for example if you follow the practice of copying dependencies to a source-controlled Lib directory. There was a bug where we would falsely report a circular association if you had an EntityHolder with no corresponding foreign key field. This is fixed in recent nightly builds. So your clean project may be using the latest nightly but your "real" project may still be on RTM or an older nightly. |
|
|
[quote user="ivan"]Your clean and "real" projects may be referencing different versions of the LightSpeed DLL[/quote] Thank you Ivan, you were right - I have had an old LightSpeed library in Source Control Server. Updating dll solved problem. Little question: Is it possible to specify TransientAttribute for backreference Id field in child table on one-to-many association without be rewritten with designer saving. |
|
|
No. The backreference ID field is the foreign key for the association, and must therefore not be transient. If you don't want the autogenerated foreign key field (backreference ID), you must either specify Key Property Reference, or set Generation to None and implement the EntityHolder and EntityCollection in the partial class. I think you have a composite FK which is a subset of the PK, in which case you will need to do the latter. (We are investigating whether we can improve handling of cross-cutting composite FKs, but we have not been successful so far.) |
|
|
[quote user="ivan"] I think you have a composite FK which is a subset of the PK, in which case you will need to do the latter.[/quote] Yes, this is my case. And why don't you try to add support of MetadataTypeAttribute. [MetadataType(typeof(ClientMetadata))] Where _orgId is a field (composite FK from association), which can't be defined in second part of partial Client class. But I need to set an TransientAttribute for that field on the score of you have mentioned. |
|
|
Hi LS Team, Seems support for one-to-one AssociationResolver has long been on the wish list.. any chance we can have this implemented? On an un-related note.. Christmas is right around the corner. Thanks. |
|