Reputation:
Yes, this is my fourth day in a row to ask a question about abstracts, sorry, I'll try and go answer some questions about SQLServer to return the favor to the community. Anyway...
How can I project the results of a Linq query into a abstract base class collection? Here is my my method from my RecruiterBase abstract class (there is also a corresponding CandidateBase abstract class):
public IQueryable<CandidateBase> GetCandidates()
{
return from candidates in db.Candidates
where candidates.RecruiterId == this.RecruiterId
select candidates;
}
The above method will throw a compile time error that an implicit conversion cannot be made between Candidate and CandidateBase.
modifying db.Candidates to db.Candidates.Cast() lets everything compile but I get a runtime error that no coercion operator is defined between types Candidate and CandidateBase.
I can't do: select New CandidateBase { ... } as CandidateBase since the abstract can't be implemented.
Nor can I create an explicit conversion operator between Candidate and Candidate base because it would again require me to new up my abstract
Nor can I project my results into an anonymous object and then cast to CandidateBase as I get the same runtime coercion exception between the anonymous type and CandidateBase type.
This problem came about from yesterday's question, Problem with Covariant return types from an abstract method
The answer provided by Stan R was that I was making things to complicated. I went back, simplified everything (I left the implmentation in the base and removed it from the subs) and ended up with a working GetCanidates method implemented as such:
public IQueryable<CandidateBase> GetCandidates()
{
return (from candidates in db.Candidates
where candidates.RecruiterId == this.RecruiterId
select new CandidateA
{
CandidateId = candidates.CandidateId,
LastName = candidates.LastName,
RecruiterId = candidates.RecruiterId
}).Cast<CandidateBase>();
}
The above method compiles and works, and I'm not trying to look a gift horse in the mouth, but now I have a reference to my subtype in my base type (when I project the results into CandidateA) and that just seems odd. Feel free to vote my question down if the reference to the subtype from within the base type is okay.
Thanks.
Full class defs:
public abstract class RecruiterBase
{
public int RecruiterId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public RecruiterBase()
{
}
public RecruiterBase(int id)
{
DataClasses1DataContext db = new DataClasses1DataContext();
Recruiter rc = db.Recruiters.SingleOrDefault(r => r.RecruiterId == id);
this.RecruiterId = rc.RecruiterId;
this.FirstName = rc.FirstName;
this.LastName = rc.LastName;
}
public IQueryable<CandidateBase> GetCandidates()
{
DataClasses1DataContext db = new DataClasses1DataContext();
return (from candidates in db.Candidates
where candidates.RecruiterId == this.RecruiterId
select new CandidateA
{
CandidateId = candidates.CandidateId,
LastName = candidates.LastName,
FirstName = candidates.FirstName,
RecruiterId = candidates.RecruiterId
}
).Cast<CandidateBase>();
}
}
public abstract class TempCandidateBase
{
public int CandidateId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? RecruiterId { get; set; }
public CandidateBase()
{
}
public CandidateBase(int id)
{
DataClasses1DataContext db = new DataClasses1DataContext();
Candidate candidate = db.Candidates.SingleOrDefault(c => c.CandidateId == id);
this.CandidateId = candidate.CandidateId;
this.FirstName = candidate.FirstName;
this.LastName = candidate.LastName;
this.RecruiterId = candidate.RecruiterId;
}
}
public class RecruiterA : RecruiterBase
{
public RecruiterA()
: base()
{
}
public RecruiterA(int id)
: base(id)
{
}
}
public class CandidateA : CandidateBase
{
public CandidateA()
: base()
{
}
public CandidateA(int id)
: base(id)
{
}
}
Upvotes: 0
Views: 1694
Reputation: 16065
Justin, it is not odd..it is indeed what inheritance is intended for. Your CandidateBase class provides a base for your Candidate classes and because it is abstract it means that it provides some logic that you don't have to worry about later on. I think it is better explained with an example.
Lets say you have 2 different Candidate classes and they both want to provide some functionality, lets say GetResume().. you may create an abstract class or an interface for this..in your case you made an abstract class as such
public class CandidateBase
{
//some logic that you might need to share between Candidates
//such as storing Name, Age..etc
// your abstract method
public abstract String GetResume();
}
now lets say CandidateA gets his Resume from a specific Web Service
public class CandidateA : CandidateBase
{
public String GetResume()
{
//some logic to get Resume from some web service
return resumeStr;
}
}
now lets say you have CandidateB and you store his resume somewhere on disk.
public class CandidateB : CandidateBase
{
public String GetResume()
{
//some logic to get Resume from disk
return resumeStr;
}
}
at some point your code when you call GetCandidates() you won't have to worry about which type your candidate is, you can still get their resume by calling GetResume() on CandidateBase.
btw Justin, if it really bothers you, you can always cast back to CandidateA after you call GetCandidates()
IQueryable<CandidateA> candidates = recruiterClass.GetCandidates().Cast<CandidateA>();
EDIT TO ADD
Justin I think this should fix your issue, let me know.
public abstract class CandidateBase
{
public int CandidateId { get; set; }
public string LastName { get; set;}
public string FirstName { get; set;}
public string RecruiterId { get; set; }
//the rest of your logic
}
public class RecruiterBase
{
// Constructors declared here
// ----HERE IS WHERE I AM BREAKING DOWN----
public IQueryable<T> GetCandidates<T>() where T:CandidateBase, new()
{
DataClasses1DataContext db = new DataClasses1DataContext();
return (from candidates in db.Candidates
where candidates.RecruiterId == this.RecruiterId
select new T()
{
CandidateId = candidates.CandidateId,
LastName = candidates.LastName,
FirstName = candidates.FirstName,
RecruiterId = candidates.RecruiterId
}
)
}
}
you can use this like this
IQueryable<CandidateA> candidates = recruiterClass.GetCandidates<CandidateA>();
I think this is a much cleaner solution, than all those Casts :)
Upvotes: 0
Reputation: 32960
I am a bit confused...there seemed to be some edits to the original question, but when/where those edits were made is not clear. Anyway, unless I am misunderstanding something, the Candidate entity from your DataContext should derive from CandidateBase. However, based on the error you are receiving and your solution to that error, it does not appear to be doing that.
I would update your mapping to make your Candidate class derive from the CandidateBase class. Once your actual Candidate entity that your DataContext returns properly derives from the CandidateBase class, then the following should be possible:
public IQueryable<CandidateBase> GetCandidates()
{
var candidates = from candidates in db.Candidates
where candidates.RecruiterId == this.RecruiterId
select candidates;
var based = candidates.Cast<CandidateBase>();
return based;
}
Upvotes: 0
Reputation: 22719
You may need to define an ICandidate interface that Candidate and CandidateBase use, then you can return an IQueryable< ICandidate> instead.
Upvotes: 1