Есть ли OCP-дружественный способ динамически выбирать, к какой коллекции в DbContext получить доступ? - PullRequest
0 голосов
/ 24 августа 2018

Я работаю над методом извлечения коллекции записей из базы данных. Записи хранятся в отдельных таблицах на основе аспекта данных, которые они содержат. Предположим, это выглядит так.

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 таким образом, или это switch самый краткий способ достижения моя цель?

Ответы [ 3 ]

0 голосов
/ 24 августа 2018

Вы можете включить в модель IPhrase свойство «language».И затем, когда вы загружаете контекст: Result = context.Where (p => p.Language == languageParam) .ToList ()

Не уверен, будет ли это проще для вас

0 голосов
/ 24 августа 2018

Вы можете использовать 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 s, которые выможно использовать context.Phrases.OfType<EnglishPhrase>(), но так как ваш параметр определяет тип, вам нужно использовать отражение, чтобы вызвать правильный метод OfType.

Вы можете поместить все эти коды отражений в вспомогательный класс, чтобы ваше действие было чище.

В будущем, когда вы захотите добавить больше языков, вам просто нужно отредактировать перечисление PhraseType и добавить дополнительные HasValue в ваш Fluent API.Никаких изменений в методе Action не требуется.

Я только что проверил это на своей системе, и это сработало.

0 голосов
/ 24 августа 2018

Вот что приходит мне в голову.Относитесь к этому больше как к псевдокоду.Вам нужно подогнать его под свой сценарий.Но основная концепция Открыто-Закрытого Принципа включена.Я использовал класс Generic для создания языкового объекта.

Если вы хотите добавить больше языков, просто создайте новый класс.Вы можете создать какой-то класс Language, а затем класс English (и другие языки) может наследовать его.

Вы можете использовать Dependency Injection и просто манипулировать интерфейсами.

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 можно даже поместить в класс Phrase.

Надеюсь, он вам поможет.Удачи!

...