I have one EF edmx(BaseEntities). As business requirement, I created two files called.
QTDAL and AdminDAL.
Please see the following codes. My two classes (QTDAL, AdminDAL) inheritances to BaseEntities. (Note: I am not sure this is allowed to do that)
(No errors now)
I would like to have suggestions if there is any better solution to achieve as the below code.
Any advise, please ?
public void TestMethod1()
{
IQTDAL qtContext = new QTDAL();
var value = qtContext.AllCampaigns().FirstOrDefault();
//var value = qtContext.AddOrUpdateCampaign(TestCampaign);
Assert.IsNotNull(value);
}
Examples:
public partial class BaseEntities : DbContext
{
public BaseEntities()
: base("name=BaseEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Campaign> Campaigns { get; set; }
public virtual DbSet<User> Users { get; set; }
}
public class QTDAL : BaseEntities, IQTDAL
{
public IEnumerable<Campaign> AllCampaigns()
{
return Campaigns.ToList();
}
public int AddOrUpdateCampaign(Campaign campaign)
{
Campaigns.AddOrUpdate(campaign);
return SaveChanges();
}
public int DeleteCampaign(Campaign campaign)
{
Campaigns.Remove(campaign);
return SaveChanges();
}
}
public class AdminDAL : BaseEntities, IAdminDAL
{
public IEnumerable<User> AllUsers()
{
return Users;
}
public int AddOrUpdateUser(User user)
{
Users.AddOrUpdate(user);
return SaveChanges();
}
public int DeleteUser(User user)
{
Users.Remove(user);
return SaveChanges();
}
}
Well, you certainly CAN inherit from your context, but the real question is why would you?
Inheritance is a concept that is used to create an "is a" relationship. In other words, your QTDAL "is a" BaseEntities. So you can treat a QTDAL as a BaseEntities object. You do not appear to be using it that way. Instead, you seem to be wanting to wrap the BaseEntities functionality.
In which case, you should simply be using BaseEntities as a member of your QTDAL class.
Related
I have an existing C# console application that takes arguments and based on the arguments
creates an instance of markets (UK, US, MX..) using dependency injection.
Each market class does a 'string GetData()', 'string ProcessData()' and 'bool ExportData()'.
The application was initially created for one eCommerce vendor's markets. Now I am told to modify it for a different vendor that does a different process. The high-level flow remains the same.
'GetData' to fetch records from DB,
'ProcessData' for any transformation or the likes
'ExportData'.
The difference is Getdata() pulls records from DB and maps to an object. I am planning to use Petapoco. 'ProcessData' might return a similar class. 'Exportdata' currently does an API call but for the new vendor, I have to write to a file.
I was reading up on patterns I am totally confused. At first, I thought I needed abstract factory pattern and now I think the factory method is what I should be using but I am not sure if I am doing it right. Need some guidance/review here. A sample cs file I created from my understanding of factory pattern. This code is based on the headfirst code samples.
using System;
using System.Collections.Generic;
using StatusExport.Models;
namespace factorymethod
{
class Program
{
static void Main(string[] args)
{
ClientFactory factory = null;
Console.WriteLine("Enter client code:");
string clientCode= Console.ReadLine();
switch (clientCode.ToLower())
{
case "costco":
factory = new CostcoFactory("accountname", "taskname");
break;
//NEw vendor might be added
//case "walmart"
//factory = new WalmartFactory("taskname", "type");
//break
default:
break;
}
bool status = factory.ProcessData();
Console.ReadKey();
}
}
abstract class Client
{
public abstract string AccountName { get; }
public abstract string Task { get; set; }
//More properties might be added. Some may not even be used by some of the new vendors. For example, Costco Might need accountname and task. Tomorrow if walmart comes, they might not need these two or may need task and a new property 'type'
public abstract List<T> GetData<T>();
public abstract List<T> ProcessData<T>();
public abstract bool ExportData();
}
class CostcoClient : Client
{
public override string AccountName { get; }
public override string Task { get; set; }
public CostcoClient(string accountName, string task)
{
AccountName = accountName;
Task = task;
}
public override List<DBRecord> GetData<DBRecord>() //DBRecord class is specific to Costco.
{
List<DBRecord> dbresult = new List<DBRecord>();
//dbresult = db return data mapped to an object DBRecord using petapoco. Another vendor might have a different class to which DB records are mapped. So the return type can be generic
return asn;
}
public override List<T> ProcessData<T>()
{
throw new NotImplementedException(); //Any data transformation or business logic. Return type might be DBRecord or a new class altogether
}
public override bool ExportData()
{
throw new NotImplementedException();//Call API or write data to file and if success send true else false
}
}
abstract class ClientFactory
{
public abstract bool ProcessData();
}
class CostcoFactory : ClientFactory
{
public string AccountName { get; }
public string Task { get; set; }
public CostcoFactory(string accountname, string task)
{
AccountName = accountname;
Task = task;
}
public override bool ProcessData()
{
CostcoClient gc = new CostcoClient(AccountName, Task);
var result = gc.GetData<DBRecord>();
return true;
}
}
}
Do you think this is the right design approach?
I also want to keep the console project independent of vendor project. So maybe 'StatusExport.Program' for the console application. DLL projects StatusExport.Common to hold the interface and abstract classes' and 'StatusExport.Client(ex:StatusExport.Costco)' for each vendor stuff.
You can create BaseClient class that will contains a basic group of properties, and if you need to add something new - just inherit it. You did right, but i think it's better to change public modifier to protected in your properties AccountName and Task, to give access to them only from child classes.
Actually, you can create a BaseClientModels (request/response) for each method if you are not sure that returning type List will be always actual.
Example:
public abstract class BaseClient
{
#region Properties : Protected
protected abstract string AccountName { get; }
protected abstract string Task { get; set; }
#endregion
#region Methods : Public
public abstract BaseGetDataResponseModel GetData(BaseGetDataRequestModel model);
public abstract BaseProcessDataResponseModel ProcessData(BaseProcessDataRequestModel model);
public abstract BaseExportDataResponseModel ExportData(BaseExportDataRequestModel model);
#endregion
}
public class BaseGetDataResponseModel { }
public class BaseGetDataRequestModel { }
public class BaseProcessDataResponseModel { }
public class BaseProcessDataRequestModel { }
public class BaseExportDataResponseModel { }
public class BaseExportDataRequestModel { }
Then let's look on your class CostcoClient and how it can looks like:
public class CostcoClient : BaseClient
{
#region Properties : Protected
protected override string AccountName { get; }
protected override string Task { get; set; }
protected virtual IDataReader<BaseGetDataRequestModel, BaseGetDataResponseModel> DataReader { get; }
protected virtual IDataProcessor<CostcoClientProcessDataRequestModel, CostcoClientProcessDataResponseModel> DataProcessor { get; }
protected virtual IExportDataHandler<CostcoClientExportDataRequestModel, CostcoClientExportDataResponseModel> ExportDataHandler { get; }
#endregion
#region Constructors
public CostcoClient(string accountName, string task)
{
//set DataReader, DataProcessor, ExportDataHandler
AccountName = accountName;
Task = task;
}
#endregion
#region Methods : Public
public override BaseGetDataResponseModel GetData(BaseGetDataRequestModel model)
{
if (model is CostcoClientGetDataRequestModel clientGetDataRequestModel)
{
return DataReader.ReadData(clientGetDataRequestModel);
}
return null; //wrong type has passed
}
public override BaseProcessDataResponseModel ProcessData(BaseProcessDataRequestModel model)
{
if (model is CostcoClientProcessDataRequestModel clientProcessDataRequestModel)
{
return DataProcessor.ProcessData(clientProcessDataRequestModel);
}
return null;
}
public override BaseExportDataResponseModel ExportData(BaseExportDataRequestModel model)
{
if (model is CostcoClientExportDataRequestModel clientExportDataRequestModel)
{
return ExportDataHandler.Handle(clientExportDataRequestModel);
}
return null;
}
#endregion
}
public class CostcoClientGetDataRequestModel : BaseGetDataRequestModel { }
public class CostcoClientGetDataResponseModel : BaseGetDataResponseModel { }
public class CostcoClientProcessDataRequestModel : BaseProcessDataRequestModel { }
public class CostcoClientProcessDataResponseModel : BaseProcessDataResponseModel { }
public class CostcoClientExportDataRequestModel : BaseExportDataRequestModel { }
public class CostcoClientExportDataResponseModel : BaseExportDataResponseModel { }
public interface IDataReader<TIn, TOut>
{
public TOut ReadData(TIn model);
}
public interface IDataProcessor<TIn, TOut>
{
public TOut ProcessData(TIn model);
}
public interface IExportDataHandler<TIn, TOut>
{
public TOut Handle(TIn model);
}
public class CostcoClientDataReader : IDataReader<CostcoClientGetDataRequestModel, CostcoClientGetDataResponseModel>
{
public CostcoClientGetDataResponseModel ReadData(CostcoClientGetDataRequestModel model)
{
throw new NotImplementedException();
}
}
//and so on
You have to implement IDataReader, IDataProcessor, IExportDataHandler, make your logic and call it from GetData, ProcessData, ExportData methods, as an example, and get instances via dependency injection.
Then, we can change your factory to this:
public interface IClientFactory
{
BaseClient GetClientService(ClientServicesEnum value);
}
public class BaseClientFactory : IClientFactory
{
#region Propertied : Protected
protected virtual IEnumerable<BaseClient> Services { get; }
protected string AccountName { get; }
protected string Task { get; set; }
#endregion
#region Constructors
public BaseClientFactory(IEnumerable<BaseClient> services, string accountname, string task)
{
Services = services;
AccountName = accountname;
Task = task;
}
#endregion
public BaseClient GetClientService(ClientServicesEnum value)
=> Services.First(x => x.GetType().Equals(GetClientServiceByCode()[value]));
private Dictionary<ClientServicesEnum, Type> GetClientServiceByCode()
=> new Dictionary<ClientServicesEnum, Type>()
{
{ ClientServicesEnum.CostcoClient, typeof(CostcoClient) }
};
}
public enum ClientServicesEnum
{
CostcoClient = 1,
Another2 = 2,
Another3 = 3
}
Where
protected virtual IEnumerable<BaseClient> Services { get; }
you can get via DI too, and then get correct ServiceHandler by enum.
And your main function to call all this:
switch (clientCode)
{
case 1:
baseClient = ClientFactory.GetClientService(ClientServicesEnum.CostcoClient);
break;
case 2:
baseClient = ClientFactory.GetClientService(ClientServicesEnum.Another2);
break;
default:
break;
}
bool status = baseClient.ProcessData(null); //your model
The main thing is - you can use more than one pattern, for example one from Creational patterns, and one from Structural.
If i need some help in code architecture i use this:
https://refactoring.guru/
I think, using this example you can remove properties AccountName and Task, because of request models in methods.
Main class:
public class ClP_Login
{
private Form vrcView;
private I_Repository<I_Identifiable> vrcRepository = null;
public ClP_Login(Form vrpView)
{
vrcView = vrpView;
SetTheme();
}
private void SetTheme()
{
if(vrcView !=null)
vrcView.BackColor = Cl_BaseColor.StandardBackground;
}
public void CreateNewUser()
{
ClE_User test = new ClE_User();
test.Name = "test name";
test.Password = "";
Cl_RepositoryFactory vrlFactory = new Cl_RepositoryFactory();
vrcRepository = vrlFactory.CreateRepository(E_Repositories.User);
vrcRepository.Add(test);
}
}
Cl_RepositoryFactory class:
public class Cl_RepositoryFactory
{
public virtual I_Repository<I_Identifiable> CreateRepository(E_Repositories vrpRepository)
{
I_Repository<I_Identifiable> vrlRepository = null;
switch (vrpRepository)
{
case E_Repositories.User:
vrlRepository = new Cl_UserRepository() as I_Repository<I_Identifiable>;
break;
}
return vrlRepository;
}
}
Enum E_Repositories:
public enum E_Repositories
{
User
}
I_Identifiable Interface:
public interface I_Identifiable
{
int Id { get; set; }
}
I_Repository Interface:
public interface I_Repository<T>
{
T GetById(Guid id);
T GetByQuery(Queue query);
void Add(T item);
void Remove(T item);
void Update(T item);
}
Cl_UserRepository class:
public class Cl_UserRepository : I_Repository<ClE_User>
{
public void Add(ClE_User item)
{
MessageBox.Show("Created new User");
}
public ClE_User GetById(Guid id)
{
throw new NotImplementedException();
}
public ClE_User GetByQuery(Queue query)
{
throw new NotImplementedException();
}
public void Remove(ClE_User item)
{
throw new NotImplementedException();
}
public void Update(ClE_User item)
{
throw new NotImplementedException();
}
}
And ClE_User class:
public class ClE_User : I_Identifiable
{
public int Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
}
The question is, why do I get null reference exception using vrcRepository?
vrlFactory.CreateRepository(E_Repositories.User); return null and I don't have any idea why, please help
In CreateRepository method try to remove casting statement as I_Repository<I_Identifiable>. If your code will not compile, that will mean Cl_UserRepository is not compatible with I_Repository<I_Identifiable>.
Otherwise everyting is correct with CreateRepository method
ClE_User inherits from I_Identifiable, but I_Repository<ClE_User> does not inherit from I_Repository<I_Identifiable>. Those are different interfaces as far as C# is concerned.
To elaborate more, you have I_Repository<I_Identifiable> vrcRepository which should in theory take I_Repository of any I_Identifiable kind. So let's say you initialize this member to some other, for instance I_Repository<ClE_SomethingOtherThanUser>. But then you call vrcRepository.Add(test). That's not going to work, with test being ClE_User.
Now, first remove the as I_Repository<I_Identifiable> part, and then to make it compile make I_Repository just a plain dumb non-generic interface, whose methods take I_Identifiable parameter or return I_Identifiable value. This may not be what you wanted, but it will compile.
EDIT
I realize that the enum will trigger. You are right
new Cl_UserRepository() as I_Repository
CL_UserRepository has to implement the interface you are trying to return, and then you don't need to type cast it at all. Sorry! I owe you a case of beer.
I have just learned about GraphDiff, and how it is supposed to take care of all the differences between the disconnected entity and the one stored in the database.
The thing is that I do not know how to use GraphDiff, I tried the documentation, but I didn't understand it well.
I am using an abstracted DBContext, through an Interface and using DbSet so I could perform Unit Testing on them:
public interface IDbRepositories
{
IDbSet<Client> ClientsDB { get;}
AppIdentityDbContext DB { get; }
IDbSet<Contacts> ContactsDB { get; }
IDbSet<ExposureResult> ExposureDB { get; }
IDbSet<OrderMods> OrderModsDB { get; }
IDbSet<ProductDetails> ProductDetailsDB { get; }
IDbSet<OrderProcess> OrderProcessDB { get; }
IDbSet<Order> OrderDB { get; }
void SaveChanges();
}
This is the actual class implementing the interface:
public class DbRepositories : IDbRepositories
{
private AppIdentityDbContext db = new AppIdentityDbContext();
//Get DB Context. This is done this way, so a Mock can be injected when testing
public IDbSet<Client> ClientsDB
{
get { return db.Clients; }
}
public AppIdentityDbContext DB
{
get { return db; }
}
public IDbSet<Contacts> ContactsDB
{
get { return db.Contacts; }
}
public IDbSet<ExposureResult> ExposureDB
{
get { return db.ExposureTBL; }
}
public IDbSet<OrderMods> OrderModsDB
{
get { return db.OrderMods; }
}
public IDbSet<ProductDetails> ProductDetailsDB
{
get { return db.ProductDetailsTBL; }
}
public IDbSet<OrderProcess> OrderProcessDB
{
get { return db.OrderProcesses; }
}
public IDbSet<Order> OrderDB
{
get { return db.OrderTBL; }
}
public void SaveChanges()
{
this.db.SaveChanges();
}
}
Now, the problem part is in here:
public bool SaveOrderChanges(OrderProcess[] Order, int OrderID, int uid)
{
//2nd Step:
var ComparableObject = dbs.OrderProcessDB.Where(x => x.OrderID == OrderID).ToList();
var Objections = dbs.OrderDB.Where(x => x.OrderID == OrderID).FirstOrDefault();
dbs.DB.UpdateGraph(dbs.OrderDB, m => m.OwnedCollection());
dbs.SaveChanges();
return true;
}
I'd like to tell the differences between the Order parameter and the one I extract from OrderProcessDB. These are a One to Many Relationship.
I do not know how to use GraphDiff for this scenario. Any ideas?
You could just expose the base DbContext object in the interface, but that would violate basic principles of encapsulation. The challenge is that the UpdateGraph method is a static extension off of the concrete DbContext class. Here is my solution:
First the interface:
public interface IMyDbContext
{
...
TEntity UpdateGraph<TEntity>(TEntity entity, Expression<Func<IUpdateConfiguration<TEntity>, object>> mapping = null) where TEntity : class, new();
}
Then the actual DbContext:
public class MyDbContext : DbContext, IMyDbContext
{
...
public TEntity UpdateGraph<TEntity>(TEntity entity, Expression<Func<IUpdateConfiguration<TEntity>, object>> mapping = null) where TEntity : class, new()
{
return ((DbContext)this).UpdateGraph(entity, mapping);
}
}
And lastly example usage inside of a repository:
public class MyRepository : IMyRepository
{
private readonly IMyDbContext _myDbContext;
public MyRepository (IMyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
public async Task<SomeEntity> UpdateSomeEntity(SomeEntity updatedSomeEntity)
{
_myDbContext.UpdateGraph(updatedSomeEntity, map => map.OwnedCollection(p => p.SomeChildCollection));
await _myDbContext.SaveChangesAsync();
return updatedSomeEntity;
}
}
I realize this is old, but I just found out about GraphDiff and can hopefully help anyone else looking.
This is how you use GraphDiff:
db.UpdateGraph(orderToUpdate, map => map
.AssociatedCollection(t => t.Products)
.OwnedCollection(t => t.PaymentMethods));
This says to update the Order object, and that the Order owns the PaymentMethods (meaning it can actually remove those entities), and is associated with the Products entities (meaning it will remove them from the reference table).
The short version:
In this video, Mr. Scott Allen explains how to test a controller.
But he does not show the full code of the class: FakeDbContext.
Is there someone who can help me finish it? He shows the class at, 06:15 min in the video for "testing controllers".
The long version
At school I have a elective where we learn C#. My exam project is a ASP site using MVC3.
To learn it fast, i have seen videos from PluralSight. My question is about some code that are in this video
He explains how to test controllers. So i tried:
I have made a controller which has a simple index method:
public class Round1Controller : Controller
{
IDbContext _db;
public Round1Controller()
{
_db = new Entities();
}
public Round1Controller(IDbContext db)
{
_db = db;
}
public ActionResult Index()
{
var model = _db.ELECTIVES.ToList();
return View(model);
}
As you can see i have already tried to make a context.
The index method is the one i want to TEST.
The next thing he does is to make a class called, FakeDbContext, in the test project.
But sadly he only show a part of the code, and i have used a lot of hours trying to figure out how he creates a get method to a HashSet.
Here is the code that you can see from the video:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EESS.Models;
namespace EESS.Tests
{
class FakeDbContext : IDbContext
{
public IQueryable<Restaurant> Restaurants
{
get { return _map.Get<Restaurant>().asQueryable(); }
set { _map.Use<Restaurant>(value); }
}
public IQueryable<Review> Reviews
{
get { return _map.Get<Review>().asQueryable(); }
set { _map.Use<Review>(value); }
}
public int SaveChanges()
{
ChangesSaved = true;
return 0;
}
public bool ChangesSaved { get; set; }
public T Attach<T>(T entity) where T : class
{
_map.Get<T>().Add(entity);
return entity;
}
public T Add<T>(T entity) where T : class
{
_map.Get<T>().Add(entity);
return entity;
}
public T Delete<T>(T entity) where T : class
{
_map.Get<T>().Remove(entity);
return entity;
}
SetMap _map = new SetMap();
class SetMap : KeyedCollection<Type, object>
{
public HashSet<T> Use<T>(IEnumerable<T> sourceData)
{
var set = new HashSet<T>(sourceData);
if (Contains(typeof(T)))
{
Remove(typeof(T));
}
Add(set);
return set;
}
}
}
}
To end the long version, my problem is i get a error on _Map.Get.
Does not contain a definition or extension method.
EDIT! PART 2:
After #xelibrion great answer it finally worked.
But then another problem come up.
The IDbContext class looks like this:
public interface IDbContext
{
IQueryable<ELECTIVES> ELECTIVES { get; }
int SaveChanges();
T Attach<T>(T entity) where T : class;
T Add<T>(T entity) where T : class;
T Delete<T>(T entity) where T : class;
}
When i add this interface to my Entities Class it offcouse expect me to implement the methods.
PluralSight implements them like this:
public DbSet<ELECTIVES> electives { get; set; }
IQueryable<ELECTIVES> IDbContext.ELECTIVES
{
get { return electives; }
}
int IDbContext.SaveChanges()
{
return SaveChanges();
}
T IDbContext.Add<T>(T entity)
{
return Set<T>().Add(entity);
}
T IDbContext.Delete<T>(T entity)
{
return Set<T>().Remove(entity);
}
T IDbContext.Attach<T>(T entity)
{
var entry = Entry(entity);
entry.State = System.Data.EntityState.Modified;
return entity;
return Set<T>().Add(entity);
}
But my "_dbModel.Designer.cs" class from my entity model does not know what Set and Entry is, and only suggest i make a method stub. There are a lot more code in this class, so if its need just ask for the rest :) I have changed Restaurants to Electives since thats the table name i my DB.
Is it a "using" i have forgotten? I have seen the video again, and he does not have a method stub in his DB class.
I suppose full version of SetMap class should look like this
class SetMap : KeyedCollection<Type, object>
{
public HashSet<T> Use<T>(IEnumerable<T> sourceData)
{
var set = new HashSet<T>(sourceData);
if (Contains(typeof(T)))
{
Remove(typeof(T));
}
Add(set);
return set;
}
public HashSet<T> Get <T>()
{
return (HashSet<T>) this[typeof(T)];
}
protected override Type GetKeyForItem(object item)
{
return item.GetType().GetGenericArguments().Single();
}
}
Given the sample console application below:
QUESTION #1: Why does .Name() return typeof OranizationBuilder, but .Write() calls CorporationBuilder?
QUESTION #2: How to get .Name() to return typeof CorporationBuilder?
namespace MyCompany
{
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Environment.NewLine);
Factory.Organization()
.ID(33)
.Name("Oranization A")
.Write();
Console.WriteLine("\n----------------------------\n");
Factory.Corporation()
.Date(DateTime.Today) // Pass
.ID(44)
.Name("Company B")
// .Date(DateTime.Today) // Fail
.Write();
// QUESTION #1: Why does .Name() return typeof OranizationBuilder,
// but .Write() calls CorporationBuilder?
// QUESTION #2: How to get .Name() to return typeof CorporationBuilder?
Console.ReadLine();
}
}
/* Business Classes */
public abstract class Contact
{
public int ID { get; set; }
}
public class Organization : Contact
{
public string Name { get; set; }
}
public class Corporation : Organization
{
public DateTime Date { get; set; }
}
/* Builder */
public abstract class ContactBuilder<TContact, TBuilder>
where TContact : Contact
where TBuilder : ContactBuilder<TContact, TBuilder>
{
public ContactBuilder(TContact contact)
{
this.contact = contact;
}
private TContact contact;
public TContact Contact
{
get
{
return this.contact;
}
}
public virtual TBuilder ID(int id)
{
this.Contact.ID = id;
return this as TBuilder;
}
public virtual void Write()
{
Console.WriteLine("ID : {0}", this.Contact.ID);
}
}
public class OrganizationBuilder : ContactBuilder<Organization, OrganizationBuilder>
{
public OrganizationBuilder(Organization contact) : base(contact) { }
public virtual OrganizationBuilder Name(string name)
{
(this.Contact as Organization).Name = name;
return this;
}
public override void Write()
{
base.Write();
Console.WriteLine("Name : {0}", this.Contact.Name);
}
}
public class CorporationBuilder : OrganizationBuilder
{
public CorporationBuilder(Corporation contact) : base(contact) { }
public virtual CorporationBuilder Date(DateTime date)
{
// Cast is required, but need this.Contact to be typeof 'C'
(this.Contact as Corporation).Date = date;
return this;
}
public override void Write()
{
base.Write();
Console.WriteLine("Date : {0}", (this.Contact as Corporation).Date.ToShortDateString());
}
}
/* Factory */
public class Factory
{
public static OrganizationBuilder Organization()
{
return new OrganizationBuilder(new Organization());
}
public static CorporationBuilder Corporation()
{
return new CorporationBuilder(new Corporation());
}
}
}
EDIT/UPDATE
Here's my first attempt at a solution (see below), although I'm stuck inside the Factory and not sure how to configure the .Organization() and .Corporation() method types.
namespace MyCompany
{
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Environment.NewLine);
Factory.Organization()
.ID(33)
.Name("Oranization A")
.Write();
Console.WriteLine("\n----------------------------\n");
Factory.Corporation()
.ID(44)
.Name("Company B")
.Date(DateTime.Today)
.Write();
Console.ReadLine();
}
}
/* Business Classes */
public abstract class Contact
{
public int ID { get; set; }
}
public class Organization : Contact
{
public string Name { get; set; }
}
public class Corporation : Organization
{
public DateTime Date { get; set; }
}
/* Builder */
public abstract class ContactBuilder<TContact, TBuilder>
where TContact : Contact
where TBuilder : ContactBuilder<TContact, TBuilder>
{
public ContactBuilder(TContact contact)
{
this.contact = contact;
}
private TContact contact;
public TContact Contact
{
get
{
return this.contact;
}
}
public virtual TBuilder ID(int id)
{
this.Contact.ID = id;
return this as TBuilder;
}
public virtual void Write()
{
Console.WriteLine("ID : {0}", this.Contact.ID);
}
}
public class OrganizationBuilder<TOrganization, TBuilder> : ContactBuilder<TOrganization, TBuilder> where TOrganization : Organization where TBuilder : OrganizationBuilder<TOrganization, TBuilder>
{
public OrganizationBuilder(TOrganization contact) : base(contact) { }
public virtual TBuilder Name(string name)
{
this.Contact.Name = name;
return this as TBuilder;
}
public override void Write()
{
base.Write();
Console.WriteLine("Name : {0}", this.Contact.Name);
}
}
public class CorporationBuilder<TCorporation, TBuilder> : OrganizationBuilder<TCorporation, TBuilder> where TCorporation : Corporation where TBuilder : CorporationBuilder<TCorporation, TBuilder>
{
public CorporationBuilder(TCorporation contact) : base(contact) { }
public virtual TBuilder Date(DateTime date)
{
this.Contact.Date = date;
return this as TBuilder;
}
public override void Write()
{
base.Write();
Console.WriteLine("Date : {0}", this.Contact.Date.ToShortDateString());
}
}
/* Factory */
public class Factory
{
public static OrganizationBuilder<Organization, OrganizationBuilder> Organization()
{
return new OrganizationBuilder<Organization, OrganizationBuilder>(new Organization());
}
public static CorporationBuilder<Corporation, CorporationBuilder> Corporation()
{
return new CorporationBuilder<Corporation, CorporationBuilder>(new Corporation());
}
}
}
Here's the specific problem area:
/* Factory */
public class Factory
{
public static OrganizationBuilder<Organization, OrganizationBuilder> Organization()
{
return new OrganizationBuilder<Organization, OrganizationBuilder>(new Organization());
}
public static CorporationBuilder<Corporation, CorporationBuilder> Corporation()
{
return new CorporationBuilder<Corporation, CorporationBuilder>(new Corporation());
}
}
How to configure the OrganizationBuilder and CorportationBuilder?
When Name returns a reference, it returns this - so when the instance is actually an instance of CorporationBuilder, that reference is returned as normal. Just because the method is declared to return OrganizationBuilder doesn't mean it only returns an OrganizationBuilder reference. It returns a reference to an instance of OrganizationBuilder instance or a derived class (or null, of course).
When the Write method is then called, that's a virtual method so the execution-time type of the object is checked to find the implementation to use. The execution-time type is still CorporationBuilder, so the override specified in that type is used.
As for how to make Name() return the appropriate type - that would require more generics, basically. It can be done, but it's a pain - I've done something similar in Protocol Buffers, but it's not pleasant. You'd make OrganizationBuilder generic in TContact and TBuilder as well, and make Name return TBuilder via a cast from this to TBuilder. Then CorporationBuilder would either be generic too, or just inherit from OrganizationBuilder<Corporation, CorporationBuilder>.
EDIT: Yes, I see the problem (which I'd forgotten about before). You may want to have a concrete non-generic class called CorporationBuilder as well, to avoid the recursive generics:
public class OrganizationBuilder :
OrganizationBuilder<Organization, OrganizationBuilder>
You might also want to rename OrganizationBuilder to OrganizationBuilderBase to avoid confusion :)
(You don't need CorporationBuilder to be generic itself, if it's at the bottom of the hierarchy.)
However, this is getting extremely complicated. You might want to at least consider avoiding inheritance here. Scrap the generics, and make OrganizationBuilder have a CorporationBuilder instead of deriving from it.
Basically this pattern always gets complicated after a while - you end up needing every level to be generic apart from the leaf nodes, which always need to be nongeneric to avoid the recursion problem you've already seen. It's a pain.
The .Name() function in the OrganizationBuilder has a signature to return OrganizationBuilder type - no matter on which derived object it is called from. That is why you see it returning OrganizationBuilder. If you would have override Name() function in your contract builder and set the name to something else, you will notice that the Name() function is acting on your runtime object.
Now if you want to know how to make Name() return the builder that you want, you should follow the same technique as you used for ID() method.
EDIT/UPDATE:
Well, now I don't understand the actual error you are facing - with the new updates. Can you share the exact error you are facing?
On a side note: I feel this design is totally convoluted. I wouldn't give this to my consumers just to support a nice pattern of a builder method returning the appropriate builder object. I'd stick to much simpler approach.