是否有一种 OCP 友好的方式来动态选择 DbContext 中要访问的集合?

Posted

技术标签:

【中文标题】是否有一种 OCP 友好的方式来动态选择 DbContext 中要访问的集合?【英文标题】:Is there an OCP-friendly way to dynamically choose which collection in a DbContext to access? 【发布时间】:2018-08-24 16:48:22 【问题描述】:

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

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 是因为我稍后会添加更多语言,但这意味着每次我这样做时我都必须修改此方法。但是,我不禁觉得可能有更好的方法来做到这一点。

我可以做点别的吗,比如向IPhrase 接口添加一个Language 属性,这将允许我的方法以这种方式访问​​正确的DbSet,或者switch 是最简单的完成方式我的目标?

【问题讨论】:

如果还要添加新语言,是否还需要添加数据库表?如果是这样,我不会推荐它。一般来说,在多语言环境中添加语言永远不会导致重新编码任何内容。 你能修改它来做这样的事情吗? ***.com/questions/25302393/… @Stefan 没错。例如,要添加法语支持,我会在我的上下文中添加一个FrenchPhrase 类、一个FrenchPhrases 属性,并且(当前)在我的方法中添加一个case "French" 块。 @JAF;好的,那么您的 close for change 关注比switch 声明要广泛得多。我会重新考虑您的方法,并使用 1 张桌子。如果要很大,请使用更动态的方法。 ...顺便说一句,如果它只是应用程序中的多语言支持,您还可以使用Resource (resx) 文件。 @Stefan:就像一个新的Phrase 类的集合,包含TextLanguage 属性。通过创建一个包含所有翻译的视图并将DbContext 映射到该视图,可以将数据库中的现有表分开。谢谢!此外,实际上还有用于 UI 翻译的 resx 文件。这种方法和东西在后端用于报告生成。 【参考方案1】:

这就是我想到的。更像是伪代码。你需要把它适应你的场景。但包括开闭原则的主要概念。我使用通用类来构建语言对象。

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

您可以使用依赖注入并仅使用接口进行操作。

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

GetPhrase 方法甚至可以放在 Phrase 类中。

希望对你有帮助。祝你好运!

【讨论】:

【参考方案2】:

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

不确定这对你来说是否更容易

【讨论】:

【参考方案3】:

您可以使用 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 

数据库上下文:

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.languagePhrase";

    //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&lt;EnglishPhrase&gt;(),但是由于你的参数决定了类型,你需要使用反射来调用正确的OfType方法。

您可以将所有这些反射代码放入一个辅助类中,这样您的操作会更清晰。

以后当您想添加更多语言时,您只需编辑 PhraseType 枚举并在 Fluent API 中添加额外的 HasValue。不需要对 Action 方法进行任何更改。

我刚刚在我的系统上进行了测试,它确实有效。

【讨论】:

以上是关于是否有一种 OCP 友好的方式来动态选择 DbContext 中要访问的集合?的主要内容,如果未能解决你的问题,请参考以下文章

OCP开源项目:Spring Cloud Gateway模块中动态路由的实现

是否有一种较少冗余的方式来加载/保存实体?

是否有一种“正确”的方式来读取 CSV 文件 [重复]

是否有一种“正确”的方式来读取 CSV 文件 [重复]

是否有一种可移植的方式来获取 Python 中的当前用户名?

是否有一种可移植的方式来定义 INT_MAX?