如何 add/excecute 并最终在 C# 中的运行时将新代码删除到程序中

How to add/excecute and eventually remove new code to a program on runtime in C#

我想要实现的目标很难解释,但我会用一个具体的例子来尝试。

我想要一个程序,每隔 X 时间对我的数据库执行一些查询(在我的特定情况下,我通过实施 Quartz 使用 Jobs 来安排执行所有查询)。现在,我知道我想执行哪些查询,但我希望将来允许执行新的查询。此外,我希望在不停止程序的情况下完成这些执行。

我考虑的一个解决方案是实施一种 "Listener"。首先,有一个安排简单作业的主程序。 Job 访问已实现接口的列表,并为每个接口运行“Execute”方法。每次我想在不停止主程序的情况下对我的数据库创建一个新查询时,我都会在一个新项目中的 类 中创建执行代码,每个代码都实现一个与 Execute() 的接口方法,编写代码以执行 SQL。后来,能够(以某种方式)"register" 这些类型的“侦听器”到主程序可以访问和执行它们的方法的列表,而无需停止主程序。

此外,我会对 "unregistering" 我不想再执行的那些感兴趣。

这是一些关于 SQL 我需要执行的代码:

在我的 Program.class

static void Main(string[] args)
{
    try
    {
        ISchedulerFactory schedFact = new StdSchedulerFactory();
        IScheduler Scheduler = schedFact.GetScheduler();
        Scheduler.Start();

        IJobDetail job = null;
        ITrigger trigger = null;

        job = JobBuilder.Create<DBMapperJob>()
                    .WithIdentity("job_DBMapper_manager", "grupo_DBMapper")
                    .Build();

        trigger = TriggerBuilder.Create()
                   .StartNow()
                   .WithIdentity("job_DBMapper_manager", "grupo_DBMapper")
                   .WithSimpleSchedule(x => x
                       .WithIntervalInHours(24)
                       .RepeatForever())
                   .Build();

        Scheduler.ScheduleJob(job, trigger);
    }
    catch (Exception ex) {
        Console.Write(ex.Message);
    }
}

预定的Job

private class DBMapperJob : IJob
{
    public void Execute(IJobExecutionContext context)
    {          
        ExecuteSQL();
    }
}

执行SQL的方法示例:

private static void ExecuteSQL()
{    
    DbProviderFactory ibmFactory = DbProviderFactories.GetFactory("IBM.Data.DB2");
    using (DbConnection ibmConnection = ibmFactory.CreateConnection())
    {
        ibmConnection.ConnectionString = ibmConnectionString;
        ibmConnection.Open();

        using (DbCommand command = ibmConnection.CreateCommand())
        {
            command.CommandText = select_sql_string

            using(IDataReader reader = command.ExecuteReader())
            {
                //in many cases I need to analyze the results and store the data in objects in order to make more queries to the database.
                ...
            }
        }

        ibmConnection.Close();

        //Later I process the data
        ...
    }


    DbProviderFactory pgeFactory = DbProviderFactories.GetFactory("Npgsql");
    using (var pgeConnection = pgeFactory.CreateConnection())
    {
        pgeConnection.ConnectionString = pgeConnectionString;
        pgeConnection.Open();
        using (var command = pgeConnection.CreateCommand())
        {
            command.CommandText = truncate_sql_string;
            command.ExecuteNonQuery();
        }
        foreach (objects_processed_list)
        {
            using (var command = pgeConnection.CreateCommand())
            {
                command.CommandText = "insert_sql_string";
                command.ExecuteNonQuery();
            }
        }
        pgeConnection.Close();
    }
}

任何有助于实现此目的的帮助或您可以想到的任何其他解决方案以解决此问题,我将不胜感激。感谢您的帮助!

感谢@Jan 向我展示了解决问题的第一步。

解决方案:使用 AppDomain(通过反射加载程序集)、接口(使插件使用相同的语言)和 Quartz(安排 SQL 执行)。它是这样工作的:

3 个项目:管理器,一个包含接口定义,另一个 类 实现接口(并将执行我的 SQL)。

接口定义项目有下一个接口:

public interface IJobExecution
{
    void SqlExecute();
}

包含将执行我的 SQL 的 类 的项目:

[Serializable]
public class Ejecucion2 : MarshalByRefObject, IJobExecution
{
    public void SqlExecute()
    {
        Console.WriteLine("Here I execute the SQL");
    }
}

Manager项目会有执行程序,周期性启动进程执行接口中定义的方法:

class Program
{
    private static IScheduler _scheduler;

    static void Main(string[] args)
    {
        ISchedulerFactory schedFact = new StdSchedulerFactory();
        ISchedulerListener schedListener = new SchedulerListener();
        _scheduler = schedFact.GetScheduler();
        _scheduler.ListenerManager.AddSchedulerListener(schedListener);
        _scheduler.Start();

        UpdateJobs();

        string option = "";

        do
        {
            Console.WriteLine("Menu");
            Console.WriteLine("1 - Update Jobs");
            option = Console.ReadLine();
            switch (option)
            {
                case "1":
                    UpdateJobs();
                    break;
            }
        }
        while (true);

        Console.ReadLine();
    }

    private static void UpdateJobs()
    {
        //Delete old jobs
        foreach (String groupName in _scheduler.GetJobGroupNames())
        {
            foreach (JobKey newJobKey in _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupContains(groupName)))
            {
                _scheduler.DeleteJob(newJobKey);
            }
        }

        //Create new jobs
        List<Tuple<string, string>> pathsNamesDll = GetDllPathsAndNames();

        int jobNr = 1;
        foreach (Tuple<string,string> dllPathName in pathsNamesDll)
        {
            try
            {
                IJobDetail job = null;
                ITrigger trigger = null;

                job = JobBuilder.Create<MapperJob>()
                            .WithIdentity("Job_" + jobNr, "Group_" + jobNr)
                            .UsingJobData("DLL_NAME", dllPathName.Item2)
                            .UsingJobData("DLL_PATH", dllPathName.Item1)
                            .Build();

                trigger = TriggerBuilder.Create()
                       .WithIdentity("Job_" + jobNr, "Group_" + jobNr)
                       .StartAt(DateTime.Now) //To make the example easy I define all the start time to "Now"...
                       .WithSimpleSchedule(x => x
                           .WithIntervalInHours(24)//... and execute the Job every day.
                           .RepeatForever())
                       .Build();

                _scheduler.ScheduleJob(job, trigger);
                jobNr++;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

    private static List<Tuple<string, string>> GetDllPathsAndNames()
    {
        List<Tuple<string, string>> pathsNamesDll = new List<Tuple<string, string>>();
        //Here I fill the list getting the Dll Paths and the Class Names that implement the IJobExecution from an XML or some other way
        return pathsNamesDll;
    }
}    

public class MapperJob : IJob
{
    public void Execute(IJobExecutionContext context)
    {
        JobKey key = context.JobDetail.Key;
        JobDataMap dataMap = context.JobDetail.JobDataMap;
        string dllPath = dataMap.GetString("DLL_PATH");
        string dllName = dataMap.GetString("DLL_NAME");
        AppDomain domain = AppDomain.CreateDomain(key.Name + "_" + key.Group + "_Execution_Domain");
        try
        {
            IJobExecution myObject = (IJobExecution)domain.CreateInstanceFromAndUnwrap(dllPath, dllName);
            myObject.SqlExecute();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            AppDomain.Unload(domain);
        }
    }
}

有了这个解决方案,我可以:

1- 更新包含将执行我的 SQL 的 类 的 DLL,其更改将在 Manager Project 程序中自动更新。因为如果程序当时没有执行任何 dll 类,我会执行 AppDomain.Unload(domain);,所以我可以更新 dll 而无需停止管理器程序。如果到那时它正在工作,我只需要等到它完成,我可以稍后更新 dll。

2- 通过将新的 DLL 添加到我在 GetDllPathsAndNames() 方法中读取的 XML 文件并选择选项“1 - 更新作业”来添加新的 SQL 执行" 从菜单重新加载所有 DLL,而无需停止管理器程序。

3- 通过从 XML 文件中删除 DLL 并从菜单中选择选项“1 - 更新作业”以在不停止管理器程序的情况下更新 DLL 来删除现有的 SQL 执行.

通过一些小的更改,您可以使这个解决方案更加健壮,但我不想为该解决方案编写一个非常复杂的示例。