tags:

views:

416

answers:

9

I know it only allows the class to set it, but what is the point?

How do I solve the problem of having readonly ids?

Say I have a person class

public class Person
    {
        public string Name { get;  set; }
        public int Id { get; private set; }
        public int Age { get; set; }
    }

And this is in a Entities.dll, used by a GUI, BL and DAL The GUI calls the BL

   List<Person> p =  BL.PeopleBL.GetPeople();

For the sake of the example calls the DAL

...
while(dr.read())
{
    returnPersonList.add( new Person{ Age=dr.GetInt32(1), Id=dr.GetInt32(0), Name=dr.GetString(2)})
}
...

of course I cannot do that cause Id is a private set; What is the proper way to do this?

How can I let the BL/Dal set the Id, but not on the GUI?

Or is this not even the proper use of a private set?


I just wanted to add that this is your typical DB app, where the pk is the Id and should not be changed( only by the BL/DAL)


Regards

_Eric

+12  A: 

The two common approaches are either that the class should have a constructor for the DAL to use, or the DAL should use reflection to hydrate objects.

Rex M
+1 for the constructor.
Tim Jarvis
+1 for the constructor, but reflection is all wet.
kenny
I have used the CTor before, it just seemed wrong. That got me thinking why have a set when I can use a get; with a backing field. As you can guess the Id is just the pk from the data store and should not be changed.
Eric
I will accept this one as most agree with this solution, bau am going to try the InternalsVisibleTo answer below
Eric
@Kenny thats how LINQ to SQL, etc works.
Rex M
@Rex M, I assume your correct I haven't ever looked. But for the simplest solution to the question posed, it's a ctor. Thanks for the info.
kenny
+1  A: 
while(dr.read())
{
    returnPersonList.add( 
        new Person(dr.GetInt32(1), dr.GetInt32(0), dr.GetString(2)));
}

where:

public class Person
{
    public Person(int age, int id, string name) 
    {
        Age = age;
        Id = id;
        Name = name;
    }
}
John Saunders
Hi John - Just browsing SO for a bit waiting for a build seeing if I can learn stuff. I'm curious. Did you mean to leave in Id= and Name= ?
J M
@JM: Yes. It's a constructor. Why would I not leave them?
John Saunders
I know it is a ctor :) I was curious because I could not get it to compile. I copied over the class with your ctor and the 3 properties. I called the ctor on it's own i.e. var x = new Person(20, Id=1, Name="X"); This gave errors: "The name 'Id' does not exist in the current context" and same for Name. Calling the ctor without the Id= and Name= works and var y = new Person() {Name = "Blah", Age = 20}; works if you add a default ctor. I didn't know you could mix the two so I thought I try it. If it's possible, any idea why I'm getting the error?
J M
@John: `new Person(dr.GetInt32(1), Id=dr.GetInt32(0), Name=dr.GetString(2)))`
Tanzelax
@J M: You're correct, it's not possible, and that was a copy/paste error.
Tanzelax
Shame really :) I thought maybe it was something new :)
J M
@JM: I thought you meant the ones in the constructor body. Good catch of my copy/paste error (now fixed).
John Saunders
+3  A: 

Or you can do

public class Person
{
    public Person(int id)
    {
         this.Id=id;
    }

    public string Name { get;  set; }
    public int Id { get; private set; }
    public int Age { get; set; }
}
Gregoire
I have used the CTor before, it just seemed wrong. That got me thinking why have a set when I can use a get; with a backing field. As you can guess the Id is just the pk from the data store and should not be changed.
Eric
+2  A: 

Perhaps, you can have them marked as internal, and in this case only classes in your DAL or BL (assuming they are separate dlls) would be able to set it.

You could also supply a constructor that takes the fields and then only exposes them as properties.

Big Endian
This is what I tried, works great.. [assembly: InternalsVisibleTo("testDAL")]
Eric
+1  A: 

You can let the user set a read-only property by providing it through the constructor:

public class Person
{
    public Person(int id)
    {
        this.Id = id;
    }

    public string Name { get;  set; }
    public int Id { get; private set; }
    public int Age { get; set; }
}
Mark Seemann
I have used the CTor before, it just seemed wrong. That got me thinking why have a set when I can use a get; with a backing field. As you can guess the Id is just the pk from the data store and should not be changed.
Eric
A: 

This is normally the case then the ID is not a natural part of the entity, but a database artifact that needs be abstracted away.

It is a design decision - to only allow setting the ID during construction or through method invocation, so it is managed internally by the class.

You can write a setter yourself, assuming you have a backing field:

private int Id = 0;
public void SetId (int id)
{
  this.Id = id;
}

Or through a constructor:

private int Id = 0;
public Person (int id)
{
  this.Id = id;
}
Oded
I think if I used setId then any consumer of my API could change it, which is what I do not want. But I have done it this way before
Eric
A: 

Depending on the scope of my application, I like to put the object hydration mechanisms in the object itself. I'll wrap the data reader with a custom object and pass it a delegate that gets executed once the query returns. The delegate gets passed the DataReader. Then, since I'm in my smart business object, I can hydrate away with my private setters.

Edit for Pseudo-Code

The "DataAccessWrapper" wraps all of the connection and object lifecycle management for me. So, when I call "ExecuteDataReader," it creates the connection, with the passed proc (there's an overload for params,) executes it, executes the delegate and then cleans up after itself.

public class User
{
    public static List<User> GetAllUsers()
    {
        DataAccessWrapper daw = new DataAccessWrapper();
        return (List<User>)(daw.ExecuteDataReader("MyProc", new ReaderDelegate(ReadList)));
    }

    protected static object ReadList(SQLDataReader dr)
    {
        List<User> retVal = new List<User>();
        while(dr.Read())
        {
            User temp = new User();
            temp.Prop1 = dr.GetString("Prop1");
            temp.Prop2 = dr.GetInt("Prop2");
            retVal.Add(temp);
        }
        return retVal;
    }
}
Jacob G
This sounds interesting, would you have some psudo code to share?
Eric
Sample code added to my answer.
Jacob G
Thanks for adding that, seems like that won't work me as in my implementation my entities(user) exist in many tiers and know nothing of the datastore.
Eric
+3  A: 

Maybe I'm misunderstanding, but if you want truly readonly Ids why not use an actual readonly field?

public class Person
{
   public Person(int id)
   {
      m_id = id;
   }

   readonly int m_id;
   public int Id { get { return m_id; } }
}
Robert Davis
+4  A: 

This is one possible solution although not very clean

  1. Make the property you need to expose to BAL & DAL internal.
  2. Mark BAL.dll & DAL.dll "Internal Visible" in assemblyinfo.cs

    public class Person { public Person(int id) { this.Id=id; }

    public string Name { get;  set; }
    public int Id { get; internal set; }
    public int Age { get; set; }
    

    }

AssemblyInfo.cs for Entities.dll

[assembly: InternalsVisibleTo("DAL"), InternalsVisibleTo("BAL")]

That way all your internals will be visible to DAL & BAL. This may not be desirable but I'm just suggesting one possible solution

BlitzKrieg
I wonder why this did not get upvoted. Is this the wrong solution to the problem? Sound like I get the best of everything with this.
Eric
Would this still be required if I ILMerged the BL/DAL/Entites
Eric