Non avvolge un oggetto IDisposable in un'istruzione using

1

Sto aggiornando del codice per permetterci di implementare i test unitari. Quello che ho finora è un Business Layer che chiama il repository per ottenere dati dal database.

Esempio di livello aziendale:

public class ConversationLogic
{
    private IConversationData _conversationData { get; set; }

    public ConversationLogic(IConversationData conversationData)
    {
        _conversationData = conversationData;
    }

    public ConversationListModel GetConversations(int loginID)
    {
        ConversationListModel model = new ConversationListModel();

        dynamic list = _conversationData.GetConversations(loginID);

        foreach (var item in list)
        {
            ConversationModel conversation = ConvertConversation(item);

            model.Conversations.Add(conversation);
        }

        return model;
    }
}

Ciò consente l'inoltro di un'interfaccia di ConversationData, questo ConversationData è il livello del repository che chiama il database.

Esempio di archivio dati conversazione:

public class ConversationData : BaseMassiveTable, IConversationData
{
    public dynamic GetConversations(int loginID)
    {
        string sql = GetResourceFile("Project.SQL.GetConversations.sql", Assembly.GetExecutingAssembly());
        dynamic result = Query(sql, loginID).ToList();
        return result;
    }
}

Il ConversationData eredita dalla classe BaseMassive, che implementa IDisposable:

public class BaseMassiveTable : DynamicModel, IDisposable
{
    string connectionName;

    public BaseMassiveTable(string connectionStringName, string tableName, string primaryKeyColumn) :
        base(connectionStringName, tableName, primaryKeyColumn) 
    { 
        connectionName = connectionStringName;
    }

    public void Dispose()
    {
    }

    protected void WriteErrorToFile(int userId, string url, Exception ex)
    {
        try
        {
            string path = "~/Error/" + DateTime.Today.ToString("yyyy-MM-dd") + ".txt";
            if (!File.Exists(System.Web.HttpContext.Current.Server.MapPath(path)))
            {
                File.Create(System.Web.HttpContext.Current.Server.MapPath(path)).Close();
            }
            using (StreamWriter w = File.AppendText(System.Web.HttpContext.Current.Server.MapPath(path)))
            {
                w.WriteLine("\r\nLog Entry : ");
                w.WriteLine("{0}", DateTime.Now.ToString(CultureInfo.InvariantCulture));
                string err = "Error in: " + url +
                              ". Error Message:" + ex.Message;
                w.WriteLine(err);
                w.WriteLine(ex.StackTrace.ToString());
                w.WriteLine("__________________________");
                w.Flush();
                w.Close();
            }
        }
        catch
        {
            //What do we do here ????
        }
    }

    public string Truncate( string stringForTuncation, int maxLength)
    {
        string result = "";
        if (stringForTuncation == null)
        {
            result = null;
        }
        else if (stringForTuncation.Length <= maxLength)
        {
            result = stringForTuncation;
        }
        else
        {
            result = stringForTuncation.Substring(0, maxLength);
        }
        return result;
    }

    public static string GetResourceFile(string name, Assembly assembly)
    {
        using (Stream stream = assembly.GetManifestResourceStream(name))
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                string result = reader.ReadToEnd();
                return result;
            }
        }
    }

}

Come puoi vedere nella mia implementazione di ConversationLogic, non riesco a racchiudere l'interfaccia IConversationData in un utilizzo in quanto ciò non funziona con l'implementazione corrente.

Che cosa vedrai anche in ConversationData.GetConversations (), questo chiama la Massive ORM Query (), che assomiglia a questa:

public virtual IEnumerable<dynamic> Query(string sql, params object[] args)
{
    using (var conn = OpenConnection())
    {
        var rdr = CreateCommand(sql, conn, args).ExecuteReader();
        while (rdr.Read())
        {
            yield return rdr.RecordToExpando(); ;
        }
    }
}

Quello che sto cercando è che, anche se ConversationData eredita da BaseMassive, che implementa IDisposable, devo effettivamente racchiudere tutte le chiamate usando IConversationData nell'utilizzo delle istruzioni, poiché ogni chiamata al repository chiama l'istruzione Massive Query (), che gestisce le proprie risorse creando un'istruzione using () su qualsiasi query?

Chiedo scusa per la domanda prolissa, se hai bisogno di chiarimenti, fammelo sapere.

    
posta user1677922 28.06.2017 - 15:34
fonte

3 risposte

2

Il chiamante dovrebbe usare using ; è necessario implementare Dispose affinché sia in grado di farlo. Quindi le chiamate Dispose verranno messe in cascata e tutto verrà rilasciato a tempo debito.

La classe in cui viene iniettata la risorsa usa e getta dovrebbe avere il suo metodo proprio Dispose e dovrebbe rilasciare le risorse lì. Quindi devi assicurarti che chiunque stia utilizzando la classe ConversationLogic lo stia disponendo, magari con una clausola using .

Ad esempio, se la tua classe ConversationLogic viene iniettata in un controller MVC, puoi trarre vantaggio dal fatto che la classe Controller di base ha già un metodo Dispose . Devi solo sovrascriverlo per Dispose il tuo ConversationLogic, che a sua volta rilascerà il tuo servizio ad alta intensità di risorse. La pipeline .NET chiamerà il metodo Dispose del controller al momento giusto.

class MyController : System.Web.Mvc.Controller
{
    private readonly IConversationLogic _conversationLogic;

    public MyController(IConversationLogic conversationLogic)
    { 
        _conversationLogic = conversationLogic;
    }

    public override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_conversationLogic != null)
            {
                _conversationLogic.Dispose(true);
                _conversationLogic = null;
            }
            base.Dispose(true);
        }
    }
}

class ConversationLogic : IConversationLogic, IDisposable
{
    private readonly _conversationData;

    public ConversationLogic(IConversationData conversationData)
    {
        _conversationData = conversationData;
    }

    public override Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_conversationData != null)
            {
                _conversationData.Dispose();
                _conversationData = null;
            }
        }
        base.Dispose(disposing);
    }
}

A volte non sei sicuro se la risorsa iniettata implementa IDisposable. È facile da controllare.

    public override Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_conversationData != null)
            {
                var d = _conversationData as IDisposable;
                if (d != null) 
                {
                    d.Dispose();
                }
                _conversationData = null;
            }
        }
        base.Dispose(disposing);
    }

Se questo schema ti infastidisce (perché sei un purista e hai notato che il codice che ha creato la risorsa non lo ha eliminato) hai due opzioni:

  1. Passaci sopra, perché il codice che ha creato la risorsa era il contenitore IoC e richiederebbe qualcosa di veramente strano per il contenitore IoC per gestire lo smaltimento, o

  2. Non iniettare l'istanza monouso; iniettare invece una fabbrica. In questo modo la tua classe può sia creare che disporre dell'oggetto e rendere tutti felici.

    class ConversationLogic: IConversationLogic, IDisposable
    {
        private readonly _conversationData;
    
        public ConversationLogic(IConversationDataFactory conversationDataFactory)
        {
            _conversationData = conversationDataFactory.GetInstance();
        }
    
        public override Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_conversationData != null)
                {
                    _conversationData.Dispose();
                    _conversationData = null;
                }
            }
            base.Dispose(disposing);
        }
    }
    

In ogni caso, non usi using , usi Dispose override per lo schema convenzionale e assicurati che il tuo controller (o altro oggetto) sia esso stesso incluso in una clausola using in modo che Dispose viene chiamato.

    
risposta data 07.07.2017 - 02:11
fonte
2

Il tuo design attuale viola il principio di segregazione dell'interfaccia che afferma che

no client should be forced to depend on methods it does not use.

quindi il BaseMassiveTable non dovrebbe richiedere l'interfaccia IDisposable o il ConversationData non dovrebbe essere derivato da questo tipo perché sembra che non sia il giusto tipo di base per esso. Ciò significa che potresti persino aver bisogno di un ulteriore livello di astrazione che sia il BaseMassiveTable e il ConversationData possano essere derivati, ma solo il BaseMassiveTable aggiungerebbe l'interfaccia IDisposable perché introduce alcune risorse usa e getta.

In questo modo saresti in grado di utilizzare correttamente un tipo usa e getta. Altrimenti devi conoscere l'implementazione di ConversationData per sapere che in realtà non ha bisogno di essere smaltito. Questo comportamento è incoerente e qualcun altro si chiederà sicuramente cosa sta succedendo qui e se non lo sa, potrebbe effettivamente provare a correggere questo bug e disporlo correttamente. Non è giusto ora disporre un tipo usa e getta per qualsiasi motivo. Anche se ora non usa risorse non gestite, potrebbe in futuro quindi se ora ignori questa interfaccia potresti avere perdite di memoria in seguito.

    
risposta data 01.07.2017 - 17:19
fonte
1

Sì. Senza vedere che cos'è BaseMassiveTable , si può solo supporre che detenga risorse native o altre risorse IDisposable . Spetta a te liberare tali risorse non appena possibile, preferibilmente tramite l'istruzione using . Potresti, in questo caso, esaurire le risorse del pool di connessione al database.

    
risposta data 28.06.2017 - 15:58
fonte