Unable to set a business object public property using an Internal accessor

Unable to set a business object public property using an Internal accessor

Old forum URL: forums.lhotka.net/forums/t/8670.aspx


lloydm posted on Tuesday, March 16, 2010

I'm getting the compile time exception: "Property or indexer cannot be assigned to -- it is read only" when attempting to set a business object's property using an internal setter. I'm using a data access factory method defined in a separate assemby that derives from the CSLA's ObjectFactory to return a populated business object. 

I used the ProjectTrackerWithObjectFactory sample app as a template which uses an internal setter for the Resource business object's Id integer property and the project compiles, but mine won't.  Class visability and accessors in my project appear to match those in the sample app, so I'm not sure what the issue is.

My CSLA business object property declaration:

public static PropertyInfo<int> ItemIdProperty = RegisterProperty<int>(p => p.ItemId, "ID", 0);
        public int ItemId
        {
            get { return GetProperty<int>(ItemIdProperty); }
            internal set { LoadProperty(ItemIdProperty, value); }
        }

My CSLA business objects Fetch factory method:

 public static BusinessObjectType GetWidgetItem(int id)
        {
          return DataPortal.Fetch<BusinessObjectType >(new SingleCriteria<BusinessObjectType , int>(id));
        }

My data factory method(defined in a separate assembly):

public BusinessObjectType Fetch(SingleCriteria<BusinessObjectType, int> criteria)
        {
            var widgetItem= (BusinessObjectType)Activator.CreateInstance(typeof(BusinessObjectType), true);

            string sql = "SELECT description, notes FROM widgets w WHERE w.item_id = @id";

            using (var conn = new OracleConnection(Database.WidgetsConnection))
            {
                using (OracleCommand dbCmd = new OracleCommand(sql, conn))
                {
                    dbCmd.Parameters.Add("@id", OracleDbType.Varchar2, 50).Value = criteria.Value;
                    dbCmd.CommandType = CommandType.Text;
            
                    conn.Open();

                    using (var olReader = new SafeDataReader(dbCmd.ExecuteReader(CommandBehavior.CloseConnection)))
                    {
                        if (olReader.Read())
                        {
                            using (BypassPropertyChecks(dataFoundryItem))
                            {
                                widgetItem.ItemId = criteria.Value;
                                widgetItem.ItemName = olReader.GetString("data_foundry_desc");
                                widgetItem.ItemNotes = olReader.GetString("data_foundry_notes");
                            }
                        }
                        olReader.Close();
                    }
                }
            }
            MarkOld(widgetItem);
            return widgetItem;
        }

Does anyone have an idea about what I'm doing wrong?

skagen00 replied on Tuesday, March 16, 2010

Well, you're saying that Widget.ItemId is marked as internal and you're trying to set it from another assembly, which is basically not valid because the very nature of an internally marked property is that it can only be used by classes within the same assembly.

I have not used a separate data access tier as you appear to be trying to do (versus putting it in the designated places in the CSLA business objects) - so I am definitely not the expert - but I thought the advised approach in this case is to return a data-access object that your CSLA business object will use to populate itself with.

You might want to check out Rocky's Deep Data example.

http://www.lhotka.net/weblog/DNRTVShowOnCSLANETAndDataAccess.aspx

 

ajj3085 replied on Wednesday, March 17, 2010

This is correct; internal only grants access to members within the same assembly, unless you also use the InternalsVisibleTo assembly level attribute to open internals up to another, trusted assembly.  I would however recommend to steer clear of this, as it seems to cause more problems than it solves.  Here's the MSDN page:  http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx

rfcdejong replied on Wednesday, March 17, 2010

Instead of using ByPassPropertyChecks u can also use LoadProperty for each IPropertyInfo

LoadProperty(widgetItem, WidgetItem.ItemIdProperty, criteria.Value);
etc..

Lhotka used to make the static PropertyInfo registrations internal but now he recommends to make them public just like u have. Since they are public now u can just call the LoadProperty.

PS: Your factory must be inherited from ObjectFactory.

ajj3085 replied on Wednesday, March 17, 2010

Oh good point, I didn't see that they were trying for ObjectFactory.  In that case making the PropertyInfos public would be the right way to go.

JonnyBee replied on Thursday, March 18, 2010

Hi,

First -  Id recommend to make the PropertyInfo's public accessible (and not private).

Then you must make a choice on how to hide/prevent write to properties from UI and still be accessible Factory methods.You alternatives are:

1. Consider properties on BO as contract, ie hide properties some from UI and only make them visible to factory methods:

2. Make all properties public(and visible to UI)

3. Create private properties on those you want to hide from UI

In the ProjectTrackerWithObjectFactory I chose the first alternative for the internal timestamp values that I didn't want to expose to the UI while still being able to use the "Named parameters" construct on BO's (and avoid LoadProperty).

lloydm replied on Thursday, May 13, 2010

Sorry it took so long for me to reply, but thanks very much!

Copyright (c) Marimer LLC