存储状态以跨应用程序启动执行某些操作

时间:2012-12-24 23:50:16

标签: c# error-handling dependency-injection

我正在寻找一种永久存储状态+序列化类(或其他东西)的方法,这样当计算机/程序崩溃时我可以在下一个程序启动时解决问题。例如,当我上传文件时,如果没有完成,我需要在下次启动时知道。这样我就可以删除远程文件(或者如果可能的话附加到它上面)。

现在,我想出了一些基本上使用数据库(通过接口轻松交换)的伪代码来创建存储状态的表+列+行,并在作业成功完成时删除行。在每次启动时,我们现在可以从数据库中读取并根据我们上次的状态调用相应的操作。

问题:

  1. 我不确定我正在做的事情是否有必要完成这个......替代方案?
  2. 我有点希望有一个可用的图书馆,如果这是正确的方法(不需要重新发明轮子),
  3. 依赖注入怎么样?在我的代码中,我只是将类名添加到行中,但如果我想要的话 暴露所有这些插件,并有一个类名冲突?事实上,我怎么知道哪个注入的dll我必须将存储的数据传递给开头? (不知道我现在还有意义。) 或者,如果依赖项被删除,我将如何识别哪些数据库文件(如果使用文件)属于所述已删除的插件,并要求用户保留它?
  4. 到目前为止的示例代码:

    class CPersistantthingy
    {
        IDatabase mDatabase;
        string _mTablename;
        List<string> _mCols = new List<string>(new string[]{"State","SerializedClass","ClassType"}); //can't contain "ID"
    
        public Dictionary<string,string> AcquireRow()
        {
            //Make sure table exists and has the columns we need
            CreateTableIfNotExist();
            if(!VerifyTableHasColumns())
                throw new CorruptedTableException();
    
            //Create new row with State "undef", fetch the Auto Increment ID and return it
            return mDatabase.CreateRow();
        }
    
        public void WriteRow(Dictionary<string,string> row)
        {
            mDatabase.UpdateRow(row); //The database will lookup by ID and update fields.
        }
    
        private void CreateTableIfNotExist()
        {
            mDatabase.CreateTableIfNotExist(_mTablename, _mCols);
        }
    
        private bool VerifyTableHasColumns()
        {
            return mDatabase.VerifyTableHasColumns(_mTablename, _mCols);
        }
    }
    interface IDatabase
    {
        //CreateTable / CreateRow / UpdateRow etc etc
    }
    

    文件上传示例:

    States:
    Acquired //Chosen a remote path
    Started //First bytes written to server
    Failed //Upload failed
    

    如下一行:

    State: Acquired|Started|Failed
    SerializedClass: {host: "127.0.0.1", user: "usr", local:"C:\somefile.txt", remote:"/we/somefile.txt"}
    ClassType: CFileObj
    

    所以现在在我的程序中我可以像这样使用它;

    SomeDatabaseClass_Object db = new SomeDatabaseClass_Object(blabla);
    CPersistantthingy pers = new CPersistantthingy(db, "fileuploads");
    
    private void StartUpload(string localfile)
    {
        var row = pers.AcquireRow();
        row["State"] = "Acquired";
        row["ClassType"] = "CFileObj";
    
        /*Imagine that GetRemotePath reserves a remote path on the server we'll 
          upload our file to. That's how my current system works.*/
        string remfile = GetRemotePath(localfile);
        CFileObj obj = new CFileObj(currenthost, currentuser, localfile, remfile);
    
        row["SerializedClass"] = obj.Serialize();
    
        //Write the acquired state
        pers.WriteRow(row);
    
        //Uploading class has a callback to let us know when the first bytes have been written.
        try
        {
            StartUpload(obj.local, obj.remote, () => { row["State"] = "Started"; pers.WriteRow(row); } ); 
        }
        catch(Exception ex)
        {
            row["State"] = "Failed";
            pers.WriteRow(row);
            throw; //Catch at caller to immediately fix rather than waiting for next boot.
        }
    
        //Now do whatever else you want to do on a succesful upload. 
        //Maybe add new states for this too so we know at least the upload succeeded.
    
        //Finally delete the entry so it's not picked up at next boot.
        pers.DeleteByKey(row["ID"]);
    }
    

    然后确保服务器在崩溃后清除了失败的文件(不完整的上传):

    public static void Main()
    {
        SomeDatabaseClass_Object db = new SomeDatabaseClass_Object(blabla);
        CPersistantthingy pers = new CPersistantthingy(db, "fileuploads");
    
        CUploadServerObj serverObj = new CUploadServerObj(bla,di,bla);
        serverObj.Connect();
    
        //Now let's imagine a class that hooks a state to an action etc.
        var ima = new CImagineIt(pers); 
    
        /*I may have picked a bad example because I'd have to do the same for all States
          but you get the idea. */
        ima.AddStateAction("Failed", (row) => { FixFailedUpload(serverObj, pers, row); });
    
        //Read all rows from database and fire actions accordingly
        ima.DoWork();
    }
    

    在这种情况下,只需检查服务器上的文件是否小于本地文件。

    private void FixFailedUpload(CUploadServerObj serverObj, CPersistantthingy pers, Dictionary<string,string> row)
    {
        if(!row["ClassType"].Equals("CFileObj"))
        {
            //handle error
            return;
        }
    
        CFileObj obj;
        try
        {
            obj = DeSerialize(row["SerializedClass"]);
        }//Catch and handle etc etc
    
        //Are we using different credentials this boot? Then we can't check.
        if(obj.host != currenthost || obj.usr != currentuser)
        {
            //handle error and return
        }
    
        try
        {
            if(serverObj.RemoteSizeSmaller(obj.local, obj.remote))
            {
                if(serverObj.DeleteFromRemote(obj.remote))
                {
                    pers.DeleteByKey(row["ID"]);
                }
            }
        }
        catch(RemoteFileNotExistsException)
        {
            //The file didn't even exist so no worries
        }
        catch(RemoteFileDeleteException)
        {
            //The file existed but could not be removed.. it's probably time to request manual user input now because that's just weird.
        }
    }
    

    我认为这可行,但这并不理想。也许上传失败了,因为保留的路径已经被另一个进程写入了。这意味着我们无法使用Acquired状态来证明删除文件。事后检查失败原因也无济于事,因为程序可能会在两者之间崩溃。这意味着我们无论如何都被困在Acquired。或者也许我们在写入的第一个字节和它的回调之间崩溃,这意味着即使我们确实获得了我们保留的文件路径,我们仍然停留在Acquired

0 个答案:

没有答案