I'm working on a method to retrieve a collection of records from a database. The records are stored in separate tables based on an aspect of the data they contain. Suppose it looks like this.
public class EnglishPhrase : IPhrase
{
public string Text {get; set;}
}
public class SpanishPhrase: IPhrase
{
public string Text {get; set;}
}
// This is actually a DbContext with DbSets.
// I have not implemented DbContext in this example to
// alleviate overhead when reproducing the situation.
public class MyContext
{
public EnglishPhrase[] EnglishPhrases { get; set; }
public SpanishPhrase[] SpanishPhrases { get; set; }
}
My method needs to pick either English or Spanish phrases based on a language argument. Right now I'm accomplishing it with a switch
statement.
public IEnumerable<IPhrase> GetPhrases(string language)
{
IEnumerable<IPhrase> result = null;
MyContext context = new MyContext();
switch(language)
{
case "English":
result = context.EnglishPhrases.ToList();
break;
case "Spanish":
result = context.SpanishPhrases.ToList();
break;
default:
throw new Exception();
}
return result;
}
I used the switch
because I'm going to be adding more languages later on, but that means I'll have to modify this method every time I do that. However, I can't help but feel like there could be a better way to do this.
Could I do something else, such as adding a Language
property to the IPhrase
interface, that would allow my method to access the right DbSet
that way, or is the switch
the tersest way to accomplish my goal?
You can use TPH to simplify you context and action.
Models:
public enum PhraseType
{
English,
Spanish
}
public abstract class Phrase
{
public int Id { get; set; }
public string Text { get; set; }
public PhraseType PhraseType { get; set; }
}
public class EnglishPhrase : Phrase {}
public class SpanishPhrase : Phrase {}
DbContext:
public DbSet<Phrase> Phrases { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//...
modelBuilder.Entity<Phrase>()
.HasDiscriminator(p => p.PhraseType)
.HasValue<EnglishPhrase>(PhraseType.English)
.HasValue<SpanishPhrase>(PhraseType.Spanish);
}
Action method:
public IEnumerable<Phrase> GetPhrases(string language)
{
// assuming the language parameter is "english" or "spanish"
var theType = $"YourNamespace.{language}Phrase";
//assuming your models are in the current assembly
Type type = TypeInfo.GetType(theType, true, true);
MethodInfo method = typeof(Queryable).GetMethod("OfType").MakeGenericMethod(type);
var obj = appContext.Phrases.AsQueryable();
var result = method.Invoke(obj, new[] { obj });
return result as IEnumerable<Phrase>;
}
A little explanation:
You Use TPH (Table per Hierarchy) so you only need to create one DbSet
.
When you want to return all EnglishPhrase
s you can use context.Phrases.OfType<EnglishPhrase>()
but since your parameter determines the type, you need to use reflection to call the correct OfType
method.
You can put all those reflections codes into a helper class so your action will be cleaner.
In future when you want to add more languages you just have to edit the PhraseType
enum and add extra HasValue
in your Fluent API. No changes are needed in the Action method.
I just tested this on my system and it worked.