使用partial来从自动生成的linq-sql对象中抽象方法

4
背景:您好,我正在尝试构建类似于状态引擎的Windows工作流。我已经使用“Action”和“Trigger”设置了基本的引擎 - “Actions”执行自定义代码,“Triggers”是允许状态引擎从一个状态移动到另一个状态的外部事件。 "Triggers"包含许多“Actions”,当“Triggers”的“bool isMet()”条件为true时会触发这些“Actions”。
我的编程问题在于需要抽象出“Trigger”类的“isMet()”方法。原因在于,我有许多子“Trigger”类,例如继承自基本“Trigger”类的“isPaperworkCompletedTrigger”,它们每个都包含自己的定制“isMet()”代码。唯一的问题是整个引擎(如“Trigger”和“Action”)需要存储在数据库中。我首先在SQL中构建了引擎表,然后使用LINQ-to-SQL构建了我的“Action”和“Trigger”对象。通过使用partial class方法,LINQ-to-SQL确实允许您扩展自动生成的类对象,我已将isMet()方法添加到了Trigger类中,但无法使此isMet()方法抽象化,因为自动生成的Trigger类并不是抽象的(出于明显的原因)。
我尝试了通过继承my sub-classes(例如isPaperworkCompletedTrigger)中的基本Trigger类并创建一个名为isMet()的方法来软重载isMet()方法,Intellisense对此有些抱怨,并告诉我停止使用'new'关键字在该方法上。正如预期的那样,“软重载”的方法不起作用。
当从数据库中提取Trigger对象并自然调用isMet()方法时,将调用基本的isMet()方法(从Trigger类而不是子类),这是有道理的,因为数据库无法知道要调用哪个Trigger子类的isMet()方法。
显然的解决方案是在“Triggers”表中添加一个“TriggerName”字段,并根据此字段进行良好的旧切换,根据名称字段调用相应的Trigger子类的isMet()方法。但我想避免这种方法。
我希望这个项目能够允许用户“插入” 触发器(Trigger)和操作(Action)。我计划的解决方法是允许用户将自定义的基于触发器的类作为DLL文件放置在指定的文件夹中,而工作流引擎则可以使用这些类库而无需重新部署或重新构建(这样就避免了静态字符串的大型开关语句)。
这个问题的核心在于如何读取所有的触发器模块(一个DLL文件就是一个触发器模块),并调用该对象的isMet()方法(而不需要访问其类代码)。
我认为解决这个问题的攻击点在于使触发器类的isMet()方法成为抽象方法,或者加入某种转换器类来将数据库中的触发器类转换为“离线”触发器类,并使该离线类成为抽象类(从而可以重写它)。
有谁能帮助解决这个问题吗?
非常抱歉我的问题有点长,但为了让任何人都能理解这个问题,需要提供相当多的信息。
谢谢。

我认为你的问题基本上是“我能在Linq-to-sql中进行动态继承吗 - 即继承不是所有子类在编译时都已知的情况下”。 - codeulike
2个回答

2

不要将基类Trigger上的isMet()方法设为抽象方法,而是将其设为虚拟方法,可能的默认值为false。然后,您可以在派生类中使用override关键字重写该方法。

您的第二个问题涉及将触发器序列化和反序列化到数据库中。在反序列化时,您需要确保获取的是派生触发器类型,而不是基本类型。我不知道您选择了什么方法将对象序列化到数据库中,但您需要一种存储类型的方式。以为例。它将一个作为其第一个参数。如果您在序列化触发器时将typeof(DerivedTrigger)存储到数据库中的另一个字段中,那么您可以反序列化Type并使用它来将Trigger反序列化为正确的派生类型。然后调用您的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);
}

编辑

好的,使用MemoryStream似乎让情况变得混乱了。MemoryStream不是存储在数据库中的内容,它本身就是数据库。

serializedObjectType的整个原因是因为,像你所说的,使用typeof(Trigger)作为DataContractSerializer中的类型无法反序列化实际上是派生触发器的对象。因此,您需要将派生类型与对象一起存储在数据库中。

您没有说您正在使用哪种dbms,但我会使用blob来表示Trigger列,并使用varbinary或blob来表示serializedObjectType列,即Trigger的实际最终派生类型。使用硬编码类型序列化程序对类型进行序列化。即DataContractSerializer(typeof(Type), ...),并使用DataContractSerializer(typeof(T), ...)对对象进行序列化,其中T是可以使用泛型类型变量获取的派生触发器类型。

当您反序列化时,要倒序执行。首先使用硬编码的类型序列化程序(例如DataContractSerializer(typeof(Type), ...))反序列化类型,然后使用反序列化类型的结果反序列化对象。我已经更新了我的代码片段,希望更好地说明我提出的策略。抱歉回复晚了。
通常,在讨论序列化时,您只序列化对象中的值,因为这是一个对象与另一个对象之间的区别所在。您不需要将方法体序列化到数据库中,因为它存储在文件系统中的程序集中(即您在问题中提到的可插入的dll)。加载这些dll是一个完全不同的步骤。请查看System.Reflection.Assembly.LoadFile()。
从您问题中的这两个语句中:
触发器和操作需要存储在数据库中。
...用户可以将自己的自定义触发器派生类作为DLL放入指定的文件夹中。
我假设(也许不正确)类的定义将存储在文件系统上,而进入每个类对象的数据将存储在数据库中。如果您派生的isMet()方法是静态的(可能没有明确说明,但没有任何相关状态),那么在数据库中将没有任何东西可供存储。然而,听起来你正在设置一个Trigger持有一组Actions。在这种情况下,这些Actions被序列化到数据库中。如果您的类是Serializable,则将它们标记为公共的,否则直接将集合标记为Serializable。然后,内存流的大小将与每个触发器所持有的Actions数量成比例。清楚吗?

我已经尝试过这种方法。问题在于它不能转换为与数据库兼容的解决方案。您的本地变量(用于表示数据库存储)是TypeMemorySteam类型,我可以将MemoryStream序列化为字符串对象(与数据库兼容)。然后像您的示例中一样使用serializedObjectType进行反序列化。问题在于将serializedObjectType存储在数据库中,我该使用什么来进行反序列化?在您的Deserialize()方法中,这将无法正常工作: DataContractSerializer(typeof(Trigger), ... false, true, null); - user989056
抱歉双重发布,字数不够了。当我尝试反序列化对象时,会抛出一个序列化异常:期望来自命名空间'http://schemas.datacontract.org/2004/07/TestSerializingProject'的元素'Trigger'。遇到名称为'DerivedTrigger'的'Element' - user989056
谢谢您的回复。不幸的是,这并没有起作用(要么我没有按照您的说明操作)。我怀疑这不会起作用,因为在Serialize方法中有这一行代码:dcsObject.WriteObject(ms, source);。如果您查看将对象写入内存流后的MemoryStream的长度,那个对象的长度远远不够。第一次测试应用程序时,长度约为200字节,TriggerisMet()方法超过了200字节的代码。DataContractSerializer是否序列化方法体? - user989056
谢谢你的回答,马丁。看来我问错了问题类型。你提到的 System.Reflection.Assembly 已经基本解决了我的问题。现在我正在将每个派生的 Trigger 的类名存储在数据库中,然后通过反射在 DLL 程序集文件中找到这个类,并调用它的 isMet() 方法,而无需将对象序列化到数据库中。 - user989056
好的,太棒了!很高兴能帮上忙。很抱歉之前绕了这么久才说到重点。 - Marty Neal

0

你只是想在Linq-to-Sql中进行继承,对吗?

这并不罕见。您需要一个具有IsMet方法的基类,然后是覆盖它的适当逻辑的子类。

诀窍在于,在获取数据时让Linq-to-Sql实例化正确的子类。

我相信可以通过以下方式完成:

如何:映射继承层次结构(LINQ to SQL)
http://msdn.microsoft.com/en-us/library/bb399352.aspx

编辑:好吧,也许那行不通,因为您需要事先知道所有类来填写[InheritanceMapping]属性。因此,您要求的是Linq-to-SQL中的动态继承,编译器事先不知道子类将是什么。我不确定是否可能。

编辑2:我认为Linq-to-sql-inheritance或部分类方法都不能以动态方式执行您要求的操作。也许反射是您的最佳选择。具体而言:有一个主Trigger类,其中包含一个庞大的IsMet()方法,该方法读取TriggerType字符串,然后查找文件夹中的程序集,加载与其名称匹配的程序集,并找到相应的类并使用反射调用类上的IsMet方法,然后返回结果等等。

编辑3:或者,使用不同的ORM而不是linq-to-sql。NHibernate可能能够执行动态继承。例如,请参阅此question

编辑4:实际上,这里有人写了关于如何使用NHibernate完成您要尝试的功能的文章:“使用C#和NHibernate创建动态状态机”
http://blog.lowendahl.net/?p=164


我对状态机部分没问题。我认为你的“edit2:”评论很到位。我正在阅读这个链接http://msdn.microsoft.com/en-us/magazine/cc164170.aspx,我会回来选择我的答案。 - user989056

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接