Question:
In general, I want to make a class that will import DataTable into Access or MS SQL.
The class will have 1 method: Append, which will receive a DataTable as input, and the method, in turn, will load the DataTable to the desired destination.
Of course, the internal logic of Append in Access can be very different, for example:
- In Access, you need to ensure that the file is not higher than 2GB, otherwise the database will break
- In MS SQL, you can use BulkCopy, and in Access you will need some kind of homemade.
Accordingly, constructors can differ greatly in parameters, for example:
- The connection strings between the two types will be different
- In MS SQL, you can use or not use a transaction
- In Access, you can restore or not restore the database after each Append.
What is the best way to arrange all this?
UPD:
I have some assumptions about how this can be done, but I do not know how true this is.
Make interface:
public interface IApend
{
void Append(DataTable dt);
}
Implement it in 2 classes, each of which will contain its own import logic:
public class ImporterToSQL: IApend
{
//Какие-то поля
public void Append(DataTable dt)
{
//Какая-то логика
}
}
public class ImporterAccess : IApend
{
//Какие-то поля
public void Append(DataTable dt)
{
//Какая-то логика
}
}
And create a class like this:
public class Importer
{
IApend Concrete;
public Importer(IApend concrete)
{
Concrete = concrete;
}
static Importer ImporterToSQL(object v,object v2,object v3)
{
return new Importer(new ImporterToSQL(object v, object v2, object v3));
}
static Importer ImporterToSQL(object v, object v2)
{
return new Importer(new ImporterToSQL(object v, object v2));
}
static Importer ImporterToAccess(object v, object v2, object v3)
{
return new Importer(new ImporterAccess(object v, object v2, object v3)));
}
static Importer ImporterToAccess(object v, object v2)
{
return new Importer(new ImporterAccess(object v, object v2));
}
public void Append(DataTable dt)
{
Concrete.Append(dt);
}
}
How true is this and how well will this approach work in terms of adding new functionality (For example, I was impatient to add import from SQL to Access)? It, like, is called factory of classes?
Answer:
You are on the right track. The general interface for converting is correct. Only I would call it a little more specific, at least some kind of IImporter
.
As for creating concrete instances and your Importer
class. You really need a factory to create. However, you did not get it in its purest form. There is no point in wrapping an instance in your Importer
— you already have an interface. Just create the desired instances and return them. And the client will already call the methods directly:
public static class ImporterFactory
{
static IImporter CreateSQLImporter(object v, object v2, object v3)
{
return new ImporterToSQL(v, v2, v3);
}
static IImporter CreateAccessImporter(object v, object v2, object v3)
{
return new ImporterAccess(v, v2, v3);
}
// и так далее
}
You probably have implementation-specific data as parameters for constructors: for example, a connection string for SQL and a path to an mdb file for Access. Ideally, the client should not know anything about the implementation details at all, and the factory should contain one method per type. All details about the implementation are hidden in the factory, which slips the necessary parameters directly from the configuration (for example, from app.config). In this case, the client will not know anything about the implementation at all:
public static class ImporterFactory
{
static IImporter CreateImporter(object v)
{
var generalConfig = GetGeneralConfig();
if (generalConfig.Type == "sql")
{
var config = GetSqlImporterConfig();
return new ImporterToSQL(config.ConnectionString, v);
}
else if (generalConfig.Type == "access")
{
var config = GetAccessImporterConfig();
return new ImporterToAccess(config.ConnectionString, v);
}
}
}
This approach will work very well if you need to import to a new location. You just write another implementation of IImporter
, make changes to the ImporterFactory
and that's it. All clients that use IImporter
do not have to change. In the case of individual methods, you will have to change the calling code that controls which method to call.
As for importing from SQL into Access, such a task cannot be solved by the method described above. Above, we considered only the task "how to write data presented in a universal form (DataTable) to a specific place." The task of importing from SQL into Access can be rephrased as "how to extract data from one place and write it to another place." It is useful to break such a task into two steps and introduce an intermediate stage. Which? That's right, getting data in a universal form. So there are two steps:
- Import from source with conversion to universal view
- Export to another location
Accordingly, the two main contracts will look something like this:
interface IImporter
{
DataTable Import();
}
interface IExporter
{
void Export(DataTable data);
}
You already have the implementation of the second step, although now it is called import. If there are two steps, it would be more logical to name them with the so-called. how data moves relative to your application . You import (load) data from one source and export (upload) it to another location.
The implementation of the first step will be similar – different implementations of interfaces, a factory.
To carry out the conversion process, you will need some common entity that will connect the steps. It can accept both factories and importer/exporter instances as dependencies:
public class DataConverter // придумайте имя поудачнее, пожалуйста :)
{
private readonly IImporter _importer;
private readonly IExporter _exporter;
public DataConverter(IImporter importer, IExporter exporter)
{
_importer = importer;
_exporter = exporter;
}
public void Convert()
{
var data = _importer.Import();
_exporter.Export(data);
}
}