Ok so in the Generics, Interfaces, Providers and You series, Part 1, Part 2, Part 3 & Part 4, we made a flexible system of interfaces to make working with a data access provider model easier. In Part 4, I listed some ideas where there was room for improvement in the interfaces/class that we made. If you have not read the series yet, go ahead and take a few minutes to read them; this post will not make much sense without it. So here it is, the IReadByParentId interface(s).
Of course we need something to return the collection of items back as and this means we have a new interface in this set, IReadByParentIdDataObjectCollection.
public interface IReadByParentIdDataObjectCollection<DataObject, IdType> : IList<DataObject> where DataObject : IReadByParentIdDataObject<DataObject, IdType> { }
The interface requires that the class inherits an IList<DataObject>. Next we need the IReadByParentIdDataObject interface.
public interface IReadByParentIdDataObject<DataObject, IdType> where DataObject : IReadByParentIdDataObject<DataObject, IdType> { IdType ParentId { get; } }
This does not look that different from the IReadDataObject interface. At this point I should point out that both interfaces can be put on the same onject and the IdType between IReadDataObject and IReadByParentIdDataObject do not need to match. Next we need a provider interface...
public interface IReadByParentIdProvider<ProviderType, DataObjectTypeCollection, DataObject, IdType> where ProviderType : IReadByParentIdProvider<ProviderType, DataObjectTypeCollection, DataObject, IdType> where DataObject : IReadByParentIdDataObject<DataObject, IdType> where DataObjectTypeCollection : IReadByParentIdDataObjectCollection<DataObject, IdType> { DataObjectTypeCollection GetByParentId(IdType id, IPrincipal user); }
Aside from one more generic type in the signature for the interface and the method returning a DataObjectTypeCollection this is not that different from the IReadProvider. Now that we have the provider we need a new collection for them,
public interface IReadByParentIdProviderCollection<ProviderType, DataObjectTypeCollection, DataObject, IdType> where ProviderType : IReadByParentIdProvider<ProviderType,DataObjectTypeCollection, DataObject, IdType> where DataObject : IReadByParentIdDataObject<DataObject, IdType> where DataObjectTypeCollection : IReadByParentIdDataObjectCollection<DataObject, IdType> { ProviderType this[string name] { get; } ProviderType this[int index] { get; } void Add(ProviderBase provider); }
The changes here only involve the new generic collection type. Finally we have a new repository interface...
public interface IReadByParentIdProviderRepository<ProviderCollectionType, ProviderType, DataObjectTypeCollection, DataObject, IdType> where ProviderCollectionType : ProviderCollection, IReadByParentIdProviderCollection<ProviderType, DataObjectTypeCollection,DataObject, IdType>, new() where ProviderType : ProviderBase, IReadByParentIdProvider<ProviderType,DataObjectTypeCollection, DataObject, IdType> where DataObject : IReadByParentIdDataObject<DataObject, IdType> where DataObjectTypeCollection : IReadByParentIdDataObjectCollection<DataObject, IdType> { ProvidersRepositoryGeneric<ProviderCollectionType,ProviderType> LoadedProviders{get;} DataObjectTypeCollection GetByParentId(IdType id, IPrincipal user); DataObjectTypeCollection GetByParentId(IdType id, String providerName, IPrincipal user); DataObjectTypeCollection GetByParentId(IdType id, Int32 providerIndex, IPrincipal user); DataObjectTypeCollection GetByParentId(IdType id, ProviderType provider, IPrincipal user); }
At this point I will introduce a new object to the example tree... a Person
public class Person { public Int32 Id { get; set; } public string FirstName { get; set; } public String LastName { get; set; } private ContactInformationList _contactInformation = null; public ContactInformationList ContactInformation { get { return _contactInformation; } set { _contactInformation = value; } } }
We will also need a collection of ContactInformation so we will make one,
public class ContactInformationList : List<ContactInformation>, IReadByParentIdDataObjectCollection<ContactInformation, Int32> { }
Now lets start editing the ContactInformation objects to use our new interfaces.
First we will change the ContactInformation Object
public abstract class ContactInformation : IReadDataObject<ContactInformation, Int32>, IUpdateDataObject<ContactInformationProvider,ContactInformation>, IReadByParentIdDataObject<ContactInformation, Int32> { public static ContactInformation GetById(Int32 id, IPrincipal user) { return new ContactInformationProviderRepository().GetById(id, user); } //public event EventHandler onValidate; private String _name; public String Name { get { return _name; } set { _name = value; } } #region IReadableDataObject<ContactInformation,int> Members private Int32 _id; public int Id { get { return _id; } set { _id = value; } } #endregion #region IUpdateDataObject<ContactInformationProvider,ContactInformation> Members public void Save(IPrincipal user) { new ContactInformationProviderRepository().Update(this, user); } public virtual void Save(ContactInformationProvider provider, IPrincipal user) { provider.Update(this, user); } #endregion #region IReadByParentIdDataObject<ContactInformation,int> Members public int ParentId { get; set; } #endregion }
The only changes here are the class declaration and the addition of the ParentId property; seems simple enough; Now we will update the Provider...
public abstract class ContactInformationProvider : ProviderBase, IReadProvider<ContactInformationProvider,ContactInformation, Int32>, IUpdateProvider<ContactInformationProvider,ContactInformation>, IReadByParentIdProvider<ContactInformationProvider, ContactInformationList, ContactInformation, Int32> { #region IReadProvider<ContactInformation,int> Members public abstract ContactInformation GetById(int id, System.Security.Principal.IPrincipal user); #endregion #region IUpdateProvider<ContactInformationProvider,ContactInformation> Members public virtual void Update(ContactInformation item, System.Security.Principal.IPrincipal user) { throw new NotSupportedException("The abstract ContactInformation can not be updated."); } #endregion public abstract void Update(EmailAddress item, System.Security.Principal.IPrincipal user); public abstract void Update(PhoneNumber item, System.Security.Principal.IPrincipal user); #region IReadByParentIdProvider<ContactInformationProvider,ContactInformationList,ContactInformation,int> Members public abstract ContactInformationList GetByParentId(int id, System.Security.Principal.IPrincipal user); #endregion }
Again the only changes here are adding the interface and the new abstract method. Breaking change by adding a new abstract method we break any providers that were built before this new version. To maintain backwards compatibility you could mark the method virtual and throw a NotSupportedException, NotImplementedException, or even do something like return new ContactInformationList();.
Next we will modify the provider collection...
public class ContactInformationProviderCollection : ProviderCollection, IReadProviderCollection<ContactInformationProvider,ContactInformation, Int32>, IUpdateProviderCollection<ContactInformationProvider,ContactInformation>, IReadByParentIdProviderCollection<ContactInformationProvider, ContactInformationList, ContactInformation, Int32> { #region IReadProviderCollection<ContactInformation,int> Members public new ContactInformationProvider this[string name] { get { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(); ContactInformationProvider result = (ContactInformationProvider)base[name]; if (result == null) throw new ArgumentOutOfRangeException(); return result; } } public ContactInformationProvider this[int index] { get { if ((index < 0) || (index >= this.Count)) throw new ArgumentOutOfRangeException(); int currentIndex = 0; foreach (ContactInformationProvider item in this) { if (currentIndex == index) return item; currentIndex++; } //if we reach this point something really odd happened //in theory since we already verdified the index is 0 or greater //and is less than the total provider count this should never be hit. //it would be possible if a provider was removed during run time... //so it should blow up. throw new ArgumentOutOfRangeException(); } } public new void Add(ProviderBase provider) { //cant add a null so die if we try if (provider == null) throw new ArgumentNullException(); //check the type of the provider if (provider is ContactInformationProvider) { //all good so add the provider to the collection base.Add(provider); } else { //Invalid type so blow up. throw new ArgumentException(); } } #endregion }
In this case the only change was adding the interface to the class. Seems easy so far, now to edit the Repository...
public class ContactInformationProviderRepository : IReadProviderRepository<ContactInformationProviderCollection, ContactInformationProvider, ContactInformation, Int32>, IUpdateProviderRepository<ContactInformationProviderCollection, ContactInformationProvider, ContactInformation>, IReadByParentIdProviderRepository<ContactInformationProviderCollection, ContactInformationProvider, ContactInformationList, ContactInformation, Int32> { #region IReadProviderRepository<ContactInformationProviderCollection,ContactInformationProvider,ContactInformation,int> Members private static ProvidersRepositoryGeneric<ContactInformationProviderCollection, ContactInformationProvider> _loadedProviders = null; public ProvidersRepositoryGeneric<ContactInformationProviderCollection, ContactInformationProvider> LoadedProviders { get { if (_loadedProviders == null) { Object processLock = new Object(); lock (processLock) { if (_loadedProviders == null) { _loadedProviders = new ProvidersRepositoryGeneric<ContactInformationProviderCollection, ContactInformationProvider>(); } } } return _loadedProviders; } } public ContactInformation GetById(int id, System.Security.Principal.IPrincipal user) { return GetById(id, LoadedProviders.Provider, user); } public ContactInformation GetById(int id, string providerName, System.Security.Principal.IPrincipal user) { return GetById(id, LoadedProviders.Providers[providerName], user); } public ContactInformation GetById(int id, int providerIndex, System.Security.Principal.IPrincipal user) { return GetById(id, LoadedProviders.Providers[providerIndex], user); } public ContactInformation GetById(int id, ContactInformationProvider provider, System.Security.Principal.IPrincipal user) { if (provider == null) throw new ArgumentNullException("provider"); return provider.GetById(id, user); } #endregion #region IUpdateProviderRepository<ContactInformationProviderCollection,ContactInformationProvider,ContactInformation> Members public void Update(ContactInformation item, System.Security.Principal.IPrincipal user) { Update(item, LoadedProviders.Provider, user); } public void Update(ContactInformation item, string providerName, System.Security.Principal.IPrincipal user) { Update(item, LoadedProviders.Providers[providerName], user); } public void Update(ContactInformation item, int providerIndex, System.Security.Principal.IPrincipal user) { Update(item, LoadedProviders.Providers[providerIndex], user); } public void Update(ContactInformation item, ContactInformationProvider provider, System.Security.Principal.IPrincipal user) { if (provider == null) throw new ArgumentNullException("provider"); item.Save(provider, user); } #endregion #region IReadByParentIdProviderRepository<ContactInformationProviderCollection,ContactInformationProvider,ContactInformationList,ContactInformation,int> Members public ContactInformationList GetByParentId(int id, System.Security.Principal.IPrincipal user) { return GetByParentId(id, LoadedProviders.Provider, user); } public ContactInformationList GetByParentId(int id, string providerName, System.Security.Principal.IPrincipal user) { return GetByParentId(id, LoadedProviders.Providers[providerName], user); } public ContactInformationList GetByParentId(int id, int providerIndex, System.Security.Principal.IPrincipal user) { return GetByParentId(id, LoadedProviders.Providers[providerIndex], user); } public ContactInformationList GetByParentId(int id, ContactInformationProvider provider, System.Security.Principal.IPrincipal user) { if (provider == null) throw new ArgumentNullException("provider"); return provider.GetByParentId(id, user); } #endregion }
Well we added the the new interface to the class and added code for the new methods to the class. The new code looks a lot like the code for the GetById methods. Of course now we need to populate the list in the person object. In this case we will use a little lazy loading to populate the list.
public class Person { public Int32 Id { get; set; } public string FirstName { get; set; } public String LastName { get; set; } private ContactInformationList _contactInformation = null; public ContactInformationList ContactInformation { get { if (_contactInformation == null) _contactInformation = new ContactInformationProviderRepository() .GetByParentId(this.Id, Thread.CurrentPrincipal); return _contactInformation; } set { _contactInformation = value; } } }
the only thing that remains is to edit the dummyContactInformationProvider to handle the new method. In a real provider this would be, I think, the hardest part. You have to deal with the data store here and may, or may not, be easy (betrive anyone?).
public class dummyContactInformationProvider : ContactInformationProvider { private ContactInformationList _memoryDataStore = null; private ContactInformationList MemoryDataStore { get { if (_memoryDataStore == null) _memoryDataStore=BuildMemoryDataStore(); return _memoryDataStore; } } private ContactInformationList BuildMemoryDataStore() { ContactInformationList result = new ContactInformationList(); HomePhoneNumber phone = new HomePhoneNumber(); phone.Number = "555.555.1234"; phone.ParentId = 1; result.Add(phone); phone = new HomePhoneNumber(); phone.Number = "555.555.9999"; phone.ParentId = 2; result.Add(phone); phone = new HomePhoneNumber(); phone.Number = "555.555.1234"; phone.ParentId = 3; result.Add(phone); EmailAddress email = new EmailAddress(); email.Address = "bob@test.com"; email.ParentId = 1; result.Add(email); email = new EmailAddress(); email.Address = "dilbert@dilbert.com"; email.ParentId = 2; result.Add(email); email = new EmailAddress(); email.Address = "Jones@microsoft.com"; email.ParentId = 3; result.Add(email); return result; } public override ContactInformation GetById(int id, System.Security.Principal.IPrincipal user) { ContactInformation result = MemoryDataStore.FirstOrDefault(ci => ci.Id == id); return result; } public override void Update(EmailAddress item, System.Security.Principal.IPrincipal user) { EmailAddress email = (EmailAddress)MemoryDataStore.FirstOrDefault(ci => ci is EmailAddress && ci.Id == item.Id); email.Address = item.Address; email.ParentId = item.ParentId; } public override void Update(PhoneNumber item, System.Security.Principal.IPrincipal user) { PhoneNumber phone = (PhoneNumber)MemoryDataStore.FirstOrDefault(ci => ci is PhoneNumber && ci.Id == item.Id); phone.Number = item.Number; phone.ParentId = item.ParentId; } public override ContactInformationList GetByParentId(int id, System.Security.Principal.IPrincipal user) { ContactInformationList result = new ContactInformationList(); result.AddRange(MemoryDataStore.Where(ci => ci.ParentId == id)); return result; } }
And there you have it. There is a reason it is named "dummy" but it does the basics and will mock a real data store fairly nicely. Why did I rewrite a "dummy" provider... I now have a data store that will reset to known values each time I start the application, I can make changes to the data in my application (or unit tests) pull the modified data and when the application ends my data store is back to it's known state.
If you're interested in seeing the code itself, check out the SVN Repository at SourceFroge.net. The code samples in my blog(s) come from the project at http://sourceforge.net/projects/objecthelpdeskn.
Enjoy.