是否存在OCP友好的方法来动态选择DbContext中要访问的集合?

时间:2018-08-24 16:48:22

标签: c# .net entity-framework-core open-closed-principle

我正在研究一种从数据库检索记录集合的方法。记录根据它们包含的数据的一个方面存储在单独的表中。假设它看起来像这样。

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; }
}

我的方法需要根据语言参数选择英语或西班牙语短语。现在,我用一个switch语句来完成它。

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;
}

我使用了switch,因为稍后我将添加更多的语言,但这意味着我每次这样做都必须修改此方法。但是,我禁不住觉得有更好的方法可以做到这一点。

我是否可以做其他事情,例如向Language接口添加IPhrase属性,这将允许我的方法以这种方式访问​​正确的DbSet,或者是{{ 1}}完成目标的最捷径?

3 个答案:

答案 0 :(得分:0)

这就是我的想法。更像伪代码一样对待它。您需要使其适合您的场景。但是包括了开闭原理的主要概念。我使用泛型类来构建语言对象。

如果要添加更多语言,只需创建新类。您可以创建某种语言课,然后英语课(和其他语言)可以从中继承。

您可以使用依赖注入,并且只需使用接口即可操作。

public interface IPhrase<T>
{
    string Text { get; set; }
}

public class Phrase<T> : IPhrase<T> where T : Language
{
    public string Text { get; set; }
    public DbSet<T> DbSet { get; set; }

    public Phrase(string text, DbContext context)
    {
        Text = text;
        DbSet = context.Set<T>();
    }
}

public class Language
{
    //do smth
}
public class English : Language
{
    //do smth
}

public class Spanish : Language
{
    //do smth
}

用法:

DbContext _context = new DbContext();
Phrase<English> en = new Phrase<English>("Your english text goes here", _context);

public IEnumerable<IPhrase<T>> GetPhrases(Phrase<T> language)
{
    return language.DbSet.ToList();
}

GetPhrases方法甚至可以放在短语类中。

我希望它能对您有所帮助。祝你好运!

答案 1 :(得分:0)

您可以在IPhrase模型中包括一个“语言”属性。然后,当您加载上下文时: 结果= context.Where(p => p.Language == languageParam).ToList()

不确定这对您来说是否更简单

答案 2 :(得分:0)

您可以使用TPH简化上下文和操作。

型号:

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);
}

操作方法:

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>;
}

一些解释:

您使用TPH(每个层次结构的表),因此只需创建一个DbSet

要返回所有EnglishPhrase时,可以使用context.Phrases.OfType<EnglishPhrase>(),但是由于参数确定类型,因此需要使用反射来调用正确的OfType方法。

您可以将所有这些反射代码放入帮助程序类中,以便您的操作更加简洁。

将来,当您想添加更多语言时,只需编辑PhraseType枚举并在Fluent API中添加额外的HasValue。无需更改Action方法。

我刚刚在系统上对其进行了测试,并且可以正常工作。