Абстрагирование методов от автоматически сгенерированных объектов linq-sql с использованием частичного - PullRequest
4 голосов
/ 27 февраля 2012

Справочная информация: Здравствуйте, я пытаюсь создать рабочий процесс Windows, как State Engine.У меня есть базовый движок, настроенный с Action и Trigger. Действия выполняют пользовательский код, триггеры - это внешние события, которые позволяют движку состояний переходить из одного состояния в другое.Trigger удерживает многие Actions ', которые срабатывают, когда условие Trigger' bool isMet() выполняется.

Проблема с кодированием, с которой я сталкиваюсь, заключается в том, что мне необходимо абстрагировать метод isMet()Trigger класса.Причина этого в том, что у меня есть много под Trigger классов, например, isPaperworkCompletedTrigger, которые наследуются от базового Trigger класса, и каждый из них содержит свой собственный isMet() код.Единственная сложность, с которой я столкнулся при реализации этого, заключается в том, что весь движок, например Trigger и Action, должен храниться в базе данных.Сначала я построил таблицы движков в SQL, а затем использовал LINQ-to-SQL для создания своих объектов Action и Trigger.LINQ-to-SQL позволяет вам расширять автоматически сгенерированные объекты класса, используя метод класса partial, который я использовал для добавления метода isMet() в мой класс Trigger, я не могу сделать этот метод isMet() абстрактнымпоскольку автоматически сгенерированный класс Trigger не является абстрактным (по очевидным причинам).

Я попытался «мягко переопределить» метод isMet(), унаследовав базовый класс Trigger в моих подклассах, напримерisPaperworkCompletedTrigger и создав метод с именем isMet(), intellisense немного жалуется на это и говорит мне не давать intellisense жаловаться на использование ключевого слова «new» в методе.Как и ожидалось, этот метод «мягкого переопределения» не работает.

Когда объекты Trigger извлекаются из базы данных и метод isMet() вызывается естественным образом, вызывается базовый метод isMet().(из класса Trigger, а не из подкласса), это имеет смысл, поскольку в базе данных нет возможности узнать, для какого дочернего элемента Trigger вызывать метод isMet().

ОчевидноеРешением этой проблемы является добавление поля TriggerName в таблицу Triggers и выполнение старого доброго случая переключения в этом поле, вызывая метод isMet() соответствующего подкласса Trigger в зависимости от имениполе есть.Это то, чего я хочу избежать.

Я бы хотел, чтобы этот проект позволял пользователям «подключать» Trigger и Action.Способ, которым я планирую это сделать, состоит в том, чтобы позволить пользователю помещать свои собственные пользовательские Trigger производные классы в виде библиотеки DLL в указанную папку, и механизм рабочих процессов может использовать их без повторного развертывания или перестроения (что исключаетмассивные операторы регистра переключателей для статических строк).

Суть этой проблемы заключается в том, как прочитать все модули Trigger (одна DLL является одним Trigger модулем) и вызвать isMet()метод для этого объекта (без доступа к коду его класса).

Я подозреваю, что точка атаки для решения этой проблемы заключается в том, чтобы сделать метод Trigger class isMet() абстрактным ИЛИ Поместить какой-то видпреобразователь класса для преобразования из класса Trigger базы данных в класс 'offline' Trigger и превращение этого автономного класса в абстрактный (который я могу переопределить).

Может кто-нибудь помочь с этой проблемой.

Очень извиняюсь за мой новый вопрос, но проблема требует достаточно много информации, чтобы кто-нибудь мог понять вопрос.

Спасибо

Ответы [ 2 ]

2 голосов
/ 27 февраля 2012

Вместо использования метода isMet() для базового Trigger класса abstract установите его virtual, возможно, значением по умолчанию является false.Затем вы можете override метод в производных классах, используя ключевое слово override.

Ваша вторая проблема касается сериализации и десериализации триггеров для базы данных.Когда вы десериализуете, вы хотите убедиться, что вы возвращаете производный тип триггера, а не базовый.Я не знаю, как вы решили сериализовать ваши объекты в базе данных, но вам понадобится способ сохранить тип.Например, DataContractSerializer.Он принимает Type в качестве первого параметра.Если вы сохраните typeof (DerivedTrigger) в другом поле в вашей базе данных при сериализации триггеров, вы можете десериализовать тип и использовать его для десериализации триггера до правильного производного типа.Затем вызов вашего isMet() метода должен вызвать производное переопределенное значение.Вот краткий пример использования статических переменных вместо базы данных:

[DataContract]
partial class Trigger
{
    public virtual bool isMet()
    {
        return false;
    }
}

[DataContract]
class DerivedTrigger : Trigger
{
    public object DataElement1 { get; set; }
    //and other properties to serialize.

    public override bool isMet()
    {
        return true;
    }
}

void Main()
{
    DerivedTrigger t = new DerivedTrigger();
    Serialize(t);
    ((Trigger)Deserialize()).isMet(); // returns True!
}

public static void Serialize<T>(T source)
{
    MemoryStream ms = new MemoryStream();
    Type serializedObjectType = typeof(T);

    DataContractSerializer dcsObject = new DataContractSerializer(serializedObjectType, null, int.MaxValue, false, true, null);

    dcsObject.WriteObject(ms, source); //serialize the object

    byte[] buffer = new byte[1024] //TODO:  adjust size
    ms.Position = 0;
    ms.Read(buffer, 0, 1024);

    //TODO: write buffer to database colObject here

    ms.Position = 0;
    DataContractSerializer dcsType = new DataContractSerializer(typeof(Type), null, int.MaxValue, false, true, null);
    dcsType.WriteObject(ms, serializedObjectType.DeclaringType);

    buffer = new byte[1024]
    ms.Position = 0;
    ms.Read(buffer, 0, 1024);

    //TODO: write buffer to database colType here
}

public static object Deserialize()
{
    MemoryStream ms = new MemoryStream();
    byte[] buffer = new byte[1024];

    //TODO: read colType into buffer here

    ms.Write(buffer, 0 1024);
    ms.Position = 0;

    DataContractSerializer dcsType = new DataContractSerializer(typeof(Type), null, int.MaxValue, false, true, null);
    Type serializedObjectType = dcs.Read(ms);

    //TODO: read colObject into buffer here

    DataContractSerializer dcs = new DataContractSerializer(serializedObjectType, null, int.MaxValue, false, true, null);
    return dcs.ReadObject(serializedObject);
}

EDIT

Хорошо, использование MemoryStream, похоже, запутало ситуацию.MemoryStream - это не то, что хранится в базе данных, это база данных.

Вся причина наличия serializedObjectType состоит в том, что, как вы говорите, использование typeof(Trigger) для типа в DataContractSerializer выигралоне десериализовать объекты, которые на самом деле являются производными триггерами.Следовательно, вам необходимо сохранить производный тип вместе с объектом в базе данных.

Вы не сказали, какие dbms вы используете, но я бы использовал BLOB-объект для представления столбца Trigger и либо varbinaryили большой двоичный объект для представления столбца serializedObjectType, то есть фактического наиболее производного типа триггера.Сериализуйте тип с помощью жестко закодированного сериализатора типа.то есть DataContractSerializer(typeof(Type), ...) и сериализовать объект с DataContractSerializer(typeof(T), ...), где T - это производный тип триггера, который вы можете получить с помощью переменной универсального типа.

Когда вы десериализуетесь, сделайте это в обратном порядке.Сначала десериализуйте тип, используя жестко закодированный сериализатор типов.то есть DataContractSerializer(typeof(Type), ...), а затем десериализовать объект с результатами десериализованного типа.Я обновил свои фрагменты кода, чтобы, надеюсь, лучше проиллюстрировать предложенную мной стратегию.Извините за задержку в моем ответе.

РЕДАКТИРОВАТЬ 2

Обычно, когда речь идет о сериализации, вы сериализуете только значения в объекте, поскольку именно это разделяет однообъект от другого.Вам не нужно сериализовать тело метода в базу данных, потому что оно хранится в сборках в файловой системе (подключаемые библиотеки, которые вы упоминаете в своем вопросе).Загрузка этих библиотек является отдельным шагом все вместе.посмотрите на System.Reflection.Assembly.LoadFile().

Из этих двух утверждений в вашем вопросе:

Триггеры и действия должны храниться в базе данных.

и

... пользователь должен удалить свои собственные классы, производные от триггера, в виде DLL в указанную папку.

Я предположил (возможно, неправильно), что определение длякласс будет храниться в фс, а данные, которые поступают в объекты каждого класса, будут храниться в базе данных.Если ваши производные isMet() методы являются статическими (может быть, не явно, но не имеют никакого связанного состояния), то в базе данных не будет ничего хранить.Однако, похоже, что вы настраиваете его так, что Trigger содержит коллекцию Actions.В этом случае эти Actions - это то, что сериализуется в базу данных.Просто отметьте их как общедоступные, если ваш класс Serializable, или пометьте коллекцию непосредственно как Serializable.Таким образом, размер потока памяти будет пропорционален числу Actions каждого триггера.Ясно, как грязь?

0 голосов
/ 27 февраля 2012

Вы просто пытаетесь сделать наследование в Linq-to-Sql, верно?

Это не необычно. Вам нужен базовый класс с методом IsMet, а затем подклассы, которые переопределяют его с помощью соответствующей логики.

Хитрость в том, чтобы заставить Linq-to-Sql создать экземпляр нужного подкласса при получении данных.

Я верю, что это можно сделать с помощью:

Как: сопоставить иерархии наследования (LINQ to SQL)
http://msdn.microsoft.com/en-us/library/bb399352.aspx

edit: Хорошо, возможно, это не сработает, потому что вам нужно знать все классы заранее, чтобы заполнить атрибуты [InheritanceMapping]. Итак, вы запрашиваете динамическое наследование в Linq-to-SQL, где компилятор заранее не знает, какими будут подклассы. Я не уверен, что это возможно.

edit2: чтобы делать то, что вы спрашиваете динамически, я не думаю, что Linq-to-sql-наследование сократит это. Или частичные методы. Возможно, Reflection - ваш лучший выбор. То есть: один основной класс Trigger с монстром метода IsMet (), который читает строку TriggerType, а затем ищет сборки в папке, загружает класс с соответствующим именем, находит соответствующий класс и вызывает метод класса IsMet используя отражение, затем возвращает результат ... и т.д ...

edit3: или используйте другой ORM вместо linq-to-sql. Есть шанс, что NHibernate сможет выполнять динамическое наследование. Например. смотри этот вопрос

edit4: на самом деле, вот кто-то пишет об использовании NHibernate, чтобы делать то, что вы пытаетесь сделать: «Создание динамического конечного автомата с C # и NHibernate»
http://blog.lowendahl.net/?p=164

...