从FTP下载文件后把数据放在数据库里.
相关需求是:
1.从FTP上下载文件(此FTP不知因为什么原因.下载东东时二次连接才会连上一次.FTP软件是,在.NET程序下一样如此).
2.下载文件时先要看这个文件的控制文件在不在,在才下载,不在不下载.
3.解析文件每行数据.客户会给出这个字段在这行数据中的位置.如Name(0,8),Status(8,5),T1(13,7),T2(20,30),T3(50,50).......
4.因相关要求.可以要加上一些数据库字段,字段名固定.Updatename表示文件批次.可能有的文件需要一个Ldata字段表示插入时间.
5.把数据解析后放在相应的数据表.
6.删除原文件和相关控件文件.
7.Mail报告当前情况
很简单的一个需求.开始只有二份文件,我想那好,我就用SSIS好了.也做好了.
但是后有六份文件,可能不至,吐血的是,都是上面的要求.只是在第三步会不同,字段名不同,位置不同........用SSIS我就要做六份差不多的.
然后一个有问题.可能另个五个就有相同的问题.可能就要改六次.
算了.我还是用我比较擅长的C#写吧.用什么模式也要对六个文件来生成对应的情况.还是用LinqToSQL生成的类.再加上反射与泛形解决吧.(^_^说的有的大,就是些相关小应用).
首先分析三,如Name(0,8),Status(8,5),T1(13,7),T2(20,30),T3(50,50).......
数据行分割是有规则的.就是一个接一个的分割,那么(这就要求数据库字段要和文件顺序一样,并且长度要正确.).先看一下LinqToSql为我们生成的类吧.
呵呵,可以看到上面的字段里有关于这个字段的长度信息.下面看下我的实现代码.
{
private static readonly List<PropertyInfo> ps = null;
private static readonly List<Info> infos = null;
private static readonly PropertyInfo time_Info = null;
private static readonly DateTime time = DateTime.Now;
private const int startIndex = 9;
static DataOperate()
{
if (ps == null)
{
infos = new List<Info>();
ps = typeof(T).GetProperties().ToList();
foreach (PropertyInfo item in ps)
{
if (item.ToString().Contains("String"))
{
Info info = new Info();
ColumnAttribute a = Attribute.GetCustomAttribute(item, typeof(ColumnAttribute)) as ColumnAttribute;
string length = a.DbType.Substring(startIndex, a.DbType.IndexOf(')') - startIndex);
int len = 0;
if (int.TryParse(length, out len))
{
info.Length = len;
info.ProInfo = item;
infos.Add(info);
}
}
else if(item.ToString().Contains("DateTime"))
{
time_Info = item;
}
}
}
}
public DataOperate()
{
}
//把一行数据转换成相应的对象
public bool SetData(string line, T t)
{
int start = 0;
try
{
//遍历所有正确的属性
foreach (Info info in infos)
{
if (info.ProInfo.Name != "Updatename")
{
string value = line.Substring(start, info.Length).Trim();
start += info.Length;
info.ProInfo.SetValue(t, value, null);
}
else
{
info.ProInfo.SetValue(t, FtpFileDown.SaveName, null);
}
}
if (time_Info != null)
{
time_Info.SetValue(t, time, null);
}
return true;
}
catch
{
return false;
}
}
public void GetData(string Path, List<T> ts)
{
StreamReader reader = new StreamReader(Path);
GetData(reader, ts);
}
/// <summary>
/// 把字节流转换成对应的实体
/// </summary>
/// <param name="reader"></param>
/// <param name="ts"></param>
public void GetData(StreamReader reader, List<T> ts)
{
string line = reader.ReadLine();
try
{
while ((line = reader.ReadLine()) != null)
{
T t = new T();//t = System.Activator.CreateInstance<T>();
if (this.SetData(line, t))
{
ts.Add(t);
}
else
{
//记录当前行有错误发生.
string message = string.Format("File:{0}-{1},Line:{2},Warning:{3}.<br>",typeof(T).Name,FtpFileDown.SaveName,ts.Count+2,"数据没有导入进来.可能是这行资料不全!");
message +="资料行信息:"+ line + "<br>";
FtpFileDown.Message_Body += message;
}
}
}
//最后文件读完后可能发生一个意思为不能读取已经完成的数据流(直接读网络数据时发生,读文件正确操作没有问题),属于正常情况.
catch (Exception e)
{
string message = string.Format("File:{0}-{1}发生如下异常,请确认是否正常!<br>{2}.<br>",typeof(T).Name, FtpFileDown.SaveName,e.Message);
FtpFileDown.Message_Body += message;
reader.Close();
}
//reader.Close();
}
}
public class Info
{
public PropertyInfo ProInfo { get; set; }
public int Length { get; set; }
}
下面就是关于相关FTP功能的说明 .
{
public FtpFile()
{
ControlFileExit = false;
}
public string Name { get; set; }
public string Path { get; set; }
public string SavePath { get; set; }
public string FullName
{
get
{
if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Path))
{
return Path + @"/" + Name;
}
return string.Empty;
}
}
public string ControlFileName { get; set; }
public bool ControlFileExit { get; set; }
public List<T> GetFile<T>() where T : new()
{
if (ControlFileExit)
{
DataOperate<T> t = new DataOperate<T>();
List<T> ts = new List<T>();
if (string.IsNullOrEmpty(FullName))
{
t.GetData(FullName, ts);
}
return ts;
}
return null;
}
public List<T> GetFile<T>(StreamReader data) where T : new()
{
if (ControlFileExit)
{
DataOperate<T> t = new DataOperate<T>();
List<T> ts = new List<T>();
if (data != null)
{
t.GetData(data, ts);
}
return ts;
}
return null;
}
}
public class FtpClass : IDisposable
{
public string Server { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string WorkDir { get; set; }
// private FtpWebRequest client = null;
public const int CountConnecting = 5;
public void Dispose()
{
}
//私有化无参构造函数.不让外部调用
private FtpClass()
{
}
public FtpClass(string server, string userName, string password, string workDir)
{
this.Server = server;
this.UserName = userName;
this.Password = password;
this.WorkDir = workDir;
}
//得到一个FTP客户端
public FtpWebRequest Create()
{
string url = "ftp://" + UserName + ":" + Password + "@" + Server + "/" + WorkDir;
Uri uri = new Uri(url);
FtpWebRequest client = (FtpWebRequest)FtpWebRequest.Create(uri);
client.KeepAlive = true;
client.Proxy = GlobalProxySelection.GetEmptyWebProxy(); //FtpWebRequest.GetSystemWebProxy();
return client;
}
//得到FTP服务器应答的字节流
public Stream GetStream(string method)
{
if (string.IsNullOrEmpty(method))
return null;
Stream data = null;
for (int i = 0; i < CountConnecting; i++)
{
//如果联接上了,直接跳出当前循环.
try
{
FtpWebRequest client = this.Create();
client.Method = method;
client.Timeout = 8000;
WebResponse server = client.GetResponse();
data = server.GetResponseStream();
break;
}
catch (Exception e)
{
//因为相关FTP外部原因,联接可能联不上.这个属于正常情况,可以尝试继续联接
if (e.Message.Contains("530"))
continue;
else
{
//文件不存在.另有处理.
if (!e.Message.Contains("550"))
{
string message = string.Format("发生如下异常,请确认是否正常!<br>{0}.<br>", e.Message);
FtpFileDown.Message_Body += message;
}
continue;
}
}
}
return data;
}
//得到文件夹下文件列表
public List<string> GetFileList()
{
//this.WorkDir = "EDI855";
Stream data = this.GetStream(WebRequestMethods.Ftp.ListDirectory);
if (data == null)
return null;
StreamReader reader = new StreamReader(data);
List<string> fileList = new List<string>();
while (true)
{
string file = reader.ReadLine();
if (string.IsNullOrEmpty(file))
break;
fileList.Add(file);
}
data.Close();
return fileList;
}
//把文件信息转化成对应的数据对像
public List<T> DownData<T>(FtpFile file)where T:new()
{
//当前FTP工作目录为当前文件的文件夹(用于查看文件列表)
this.WorkDir = file.Path;
List<T> files = null;
try
{
//得到当前文件的所在路径下的所有文件.
List<string> filelist = GetFileList();
//查看当前文件有没此文件的控件文件
if (filelist != null && filelist.Where(p => p.Contains(file.ControlFileName)).Count() > 0)
{
//当前FTP工作目录变成当前文件(用于来下载和删除文件)
this.WorkDir = file.FullName;
//得到当前文件的字节流.
Stream filedata = GetStream(WebRequestMethods.Ftp.DownloadFile);
if (filedata != null)
{
//控件文件存在.
file.ControlFileExit = true;
string savePath = file.SavePath + @"\" + FtpFileDown.SaveName;
//进行字节流到实体对象的转换
if (!Directory.Exists(savePath))
{
//复制文件到所需目录
FileStream fs = File.Create(savePath);
StreamReader read = new StreamReader(filedata);
StreamWriter sw = new StreamWriter(fs);
sw.Write(read.ReadToEnd());
read.Close();
sw.Close();
fs.Close();
//当前文件已经复制到相应的目录.
string message = string.Format("FileName:{0}.<br>Message:{1}.<br>", file.Name, "数据已经从FTP上下载到目地服务器,下面开始导入数据库!");
FtpFileDown.Message_Body += message;
//把当前文件的数据转换成数据对像
StreamReader reader = new StreamReader(savePath);
files = file.GetFile<T>(reader);
reader.Close();
}
filedata.Close();
//删除在FTP上的当前文件.
//GetStream(WebRequestMethods.Ftp.DeleteFile);
}
else
{
string message = string.Format("FileName:{0}.<br>Message:{1}.<br>", file.Name, "FTP上没有这个文件.");
FtpFileDown.Message_Body += message;
}
}
else
{
string message = string.Format("FileName:{0}.<br>Message:{1}.<br>", file.Name, "FTP上没有这个文件的控制文件.");
FtpFileDown.Message_Body += message;
}
}
catch (Exception e)
{
string message = string.Format("File:{0}发生如下异常,请确认是否正常!<br>{1}.<br>",file.Name, e.Message);
FtpFileDown.Message_Body += message;
}
return files;
}
}
最后要下载此类文件,只要如下代码.
FtpClass ftp = new FtpClass("----------", "-----------", "---------", null);
//关于A51Bom的处理
FtpFile file1 = new FtpFile { Name = "----------", Path = "test/----", ControlFileName = "----_ENDFILE", SavePath = @"-----------" };
//取得数据
List<A51BOM> datas1 = ftp.DownData<A51BOM>(file1);
//入数据库
if (datas1 != null)
{
db.A51BOM.InsertAllOnSubmit(datas1);
Insert_Message(file1.Name,datas1.Count);
}
总的来说,是取了个巧,其实一样有六个类,只是因为那六个类是用LinqToSql上直接拉表就行了.其中表要建好是关建.
不过总的来说,能很好解决相关问题.比如修改文件出错的异常,在SSIS上,我可能要修改六次(可能SSIS也能做到.不过我现在做不到).而用我上面程序,只要修改一个地方.那六份文件便都能应用到.
至于其中说用到了反射会不会有性能问题.就我调试而言.反射部分我是感觉不到任何执行时间长的.时间长的只有从FTP下载文件时才会有感觉.一份6000行的文件.把每行全用上面方法转换为对象时就我调试时而言我都感觉要不到1S,更不要说发布后.至于前面分析类的属性.我放在静态函数里了这样声明N个对象时都只有一份对应地址.保证多个对象不会重复构造这一部分.像单例模式那样.