前言
这个文章将不会用到数据库,另辟蹊径去实现本地的存储与读取,增删改查!~
之前写项目用到的思路,因为是非常小的项目,不想依赖数据库来增删改查,以此避免复杂的数据库环境支持和安装。之前想上网查找有没有代替的方法,现在在网上几乎都是用数据库填充,没有不用数据库的,没办法,只好动手自己写了!
开始之前,容我说明本文章适合的情况:
①适合数据表少的项目。
②适合将数据存储在本地的项目。
③适合不想依赖复杂的数据库环境,同时想拥有存储和增删改查数据的功能的项目。
④不优雅,不聪明,但是很简单粗暴,可以快速实现
基本功能实现的思路
实现思路非常简单:
Dictionary:作为主要数据载体。增删改查在其基础上进行。
自定义类:作为数据表。字典的建立依赖于它。
JSON:用于数据存储与读取。
数据库中的数据表其实就是一个大型的键对值存储,会有一个主键,然后对应着一列数据。
Dictionary的键对值很完美契合我们的需求。
C#中,Dictionary的主要用途是提供快速的基于键值的元素查找。Dictionary的结构一般是这样的: Dictionary<[key], [value]>
Dictionary的描述
1、从一组键(Key)到一组值(Value)的映射,每一个添加项都是由一个值及其相关连的键组成
2、任何键都必须是唯一的
3、键不能为空引用null(VB中的Nothing),若值为引用类型,则可以为空值
4、Key和Value可以是任何类型(string,int,custom class 等)
①我们只需要先自定义一个类。
public class Client//根据自己需要定义类
{
public string id;//我们的主键
public string name;
public string type;
public string state;
}
②我们再定义一个Dictionary。Dictionary的Key类型是我们主键的类型,而Value内容是我们的自定义的类。
public static Dictionary<string,Client> db=new Dictionary<string,Client>();
③写一个方法,填充数据至控件之中。这里以winform的datagridview填充为例。
注意:datagridview的Column数量要和你自定义类的字段数量一致。
public void DataBind()//填充最新的数据,也可以当做刷新的方法
{
dataGridView1.Rows.Clear();//清空
//遍历字典的每一个元素
foreach (var pair in db)
{
//此时RowCount-1的大于字典的Count或者字典内没有数据则可以停止填充
if (dataGridView1.RowCount - 1 > db.Count||db.Count==0) return;
//新增行
int index = dataGridView1.Rows.Add();
//拿到类数据
var results = db[pair.Key];
dataGridView1.Rows[index].Cells[0].Value = results.id;
dataGridView1.Rows[index].Cells[1].Value = results.name;
dataGridView1.Rows[index].Cells[2].Value = results.type;
dataGridView1.Rows[index].Cells[3].Value = results.state;
}
}
④我们可能要在很多地方调用这个方法,如果会跨线程,请改写成Invoke形式
public void DataBind()//填充最新的数据,也可以当做刷新的方法
{
if (dataGridView1.InvokeRequired)//跨线程
{
//委托
dataGridView1.Invoke(new EventHandler(delegate
{
dataGridView1.Rows.Clear();
foreach (var pair in db)
{
//此时RowCount-1的大于字典的Count或者字典内没有数据则可以停止填充
if (dataGridView1.RowCount - 1 > db.Count||db.Count==0) return;
//新增行
int index = dataGridView1.Rows.Add();
//拿到类数据
var results = db[pair.Key];
dataGridView1.Rows[index].Cells[0].Value = results.id;
dataGridView1.Rows[index].Cells[1].Value = results.name;
dataGridView1.Rows[index].Cells[2].Value = results.type;
dataGridView1.Rows[index].Cells[3].Value = results.state;
}
}));
}
else//没有跨线程
{
dataGridView1.Rows.Clear();
foreach (var pair in db)
{
//此时RowCount-1的大于字典的Count或者字典内没有数据则可以停止填充
if (dataGridView1.RowCount - 1 > db.Count||db.Count==0) return;
//新增行
int index = dataGridView1.Rows.Add();
//拿到类数据
var results = db[pair.Key];
dataGridView1.Rows[index].Cells[0].Value = results.id;
dataGridView1.Rows[index].Cells[1].Value = results.name;
dataGridView1.Rows[index].Cells[2].Value = results.type;
dataGridView1.Rows[index].Cells[3].Value = results.state;
}
}
}
我们可以在很多地方调用,增删改一条数据都需要调用这个方法来进行datagridview的即时刷新。接下来就是增删改查的方法实现。
增
增加的方法很简单,首先New一个我们的自定义类用变量存储起来,将数据填入这个变量下对应的字段,之后就插入字典中,最后刷新。
public void Add()
{
Client p = new Client
{
id = Textbox1.Text,
name = Textbox2.Text,
type = Textbox3.Text,
state = Textbox4.Text
};
//先查看有没有重复数据插入
if(!db.ContainsKey(p.id))
{
db.Add(p.id, p);//以id为Key,Dictionary的Key唯一,插入数据
}
//重复主键则不能插入
else
{
MessageBox.Show("已存在此数据!请勿重复插入!");
return;
}
DataBind();//调用刷新方法,刷新最新数据
}
演示Demo-增的实现效果图
删
删除主要是要获取到主键。获取到主键即可删除字典对应的数据。以datagridview为例,就是需要获取用户点击的那一行的索引,从而获得那一行的逐渐数据。
public void Del()
{
int index = dataGridView1.CurrentRow == null ? -1 : dataGridView1.CurrentRow.Index;//获得当前行
if (index < 0) return;//如果没点击则返回,防止用户没有选中点击也会执行接下来的代码。
if (dataGridView1.Rows[index].Cells[1].Value == null) return;//选中行没有数据也返回
string id = dataGridView1.Rows[index].Cells["Id"].Value.ToString().Trim();//获得当前行的id
if (db.ContainsKey(id))//字典中有才能删,防止意外情况
{
db.Remove(id);//删除字典中的对应值
}
DataBind();//调用刷新方法,刷新最新数据
}
演示Demo-删的实现效果图
改
主要是获取到主键就能获取到信息,将数据传入你想要的位置,进行修改后保存至原来的对应键即可。注意如果是传入新窗口,则要连同数据和主窗口一起传入新窗口中,方便调用刷新代码。
public void UpDate()//改
{
int index = dataGridView1.CurrentRow == null ? -1 : dataGridView1.CurrentRow.Index;//获得当前行
if (index < 0) return;//如果没点击则返回,防止用户没有选中点击也会执行接下来的代码。
if (dataGridView1.Rows[index].Cells[1].Value == null) return;//选中行没有数据也返回
string id = dataGridView1.Rows[index].Cells["Id"].Value.ToString().Trim();//获得当前行的id
Client res = new Client();
if (db.ContainsKey(id))//字典中有才能查和删,防止意外情况
{
res = db[id];//获取到对应的数据类
}
UpDateForm form=new UpDateForm(res,this);//将数据和主窗口传入新的窗口中
form.Show();//打开新窗口
}
//改的窗口构造函数代码
Mainform form;//创建主窗口变量
Client res;//创建类实例
public UpDateForm(Client digtal,Mainform mainform)
{
InitializeComponent();
res=digtal;//数据传入
form=mainform;//窗口传入
}
//改窗口保存按钮代码
public void UpDateSave()
{
//注意主键不能修改
res.name = Textbox2.Text;
res.type = Textbox3.Text;
res.state = Textbox4.Text;
//覆盖原来的内容
Mainform.db[res.id]=res;
form.DataBind();//调用主窗口刷新方法,刷新最新数据
Close();//修改完关闭窗口
}
演示Demo-改的实现效果图
查
获取到主键,和相应想查的内容进行遍历字典,以需要过滤的条件进行改变显示表格。与DataBind类似。
以查name为例。
public void Find()
{
string nameText=TextBox1.Text;//获取name值
dataGridView1.Rows.Clear();
//遍历数据库
foreach (var pair in db)
{
if (pair.Value.name == nameText)
{
//此时RowCount-1的大于字典的Count或者字典内没有数据则可以停止填充
if (dataGridView1.RowCount - 1 > db.Count||db.Count==0) return;
//新增行
int index = dataGridView1.Rows.Add();
//拿到类数据
var results = db[pair.Key];
dataGridView1.Rows[index].Cells[0].Value = results.id;
dataGridView1.Rows[index].Cells[1].Value = results.name;
dataGridView1.Rows[index].Cells[2].Value = results.type;
dataGridView1.Rows[index].Cells[3].Value = results.state;
}
}
if(dataGridView1.RowCount-1==0)
{
DataBind();//没有数据就刷新全部
MessageBox.Show("没有此数据!");
}
}
演示Demo-查的实现效果图
以上就基本可以满足代替数据表的需求了,但是始终是存在在程序内的,关闭就消失了,而打开时也会是空白,那么我们接下来就是要把数据存下来,而且在下次打开程序时读取对应的数据,做本地的存储和读取。
本地的存储与读取的实现
这里的灵感来自于《侠客风云传前传》的存档方式,当我过不了关时想修改存档时,我发现他的存档文件是一个txt文件,里面写满了,密密麻麻的JSON语句。
我瞬间悟了,它是每次读取存档,都是将JSON转为实体数据,存储存档就是将实体数据转为JSON写入txt中。
虽然不优雅且粗暴,但是很好用。
这里需要引入一个DLL。
using Newtonsoft.Json;//用于将实体类转为json语句,或者将json语句转为实体类
本地存储
每次存储的时候都是在退出的时候再做存储(如果你想在录入新数据的时候存储,也可以自己改)。
原理也很简单,我们用一个json类,每次退出前,先将目前的字典,一个个输出为一条条json语句,存储起来在本地的某个txt中即可。
将方法写入窗口的Closing方法中。
string path = Application.StartupPath;//获取程序路径
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)//关闭窗体
{
DialogResult diRes = MessageBox.Show("是否关闭程序?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Information);
if (diRes == DialogResult.OK)
{
if (!File.Exists($"{path}/config/MainSet.txt"))//如果是第一次创建
{
StreamWriter sw = new StreamWriter($"{path}/config/MainSet.txt", false);//写入文件,覆盖模式
//遍历字典
foreach (var item in db)
{
//将实体类转为字符串
string jsonStr = JsonConvert.SerializeObject(item.Value);
//写入txt
sw.WriteLine(jsonStr);
}
//关闭
sw.Close();
}
else//改写习惯
{
string all = "";
if (db.Count > 0)
{ //遍历字典
foreach (var item in db)
{
string jsonStr = JsonConvert.SerializeObject(item.Value);
all += jsonStr + "\n";
}
}
File.WriteAllText($"{path}/config/MainSet.txt", all);
}
}
else//假如用户取消了退出
{
e.Cancel = true;//取消退出
return;//结束方法
}
}
本地读取
每次读取是在程序打开的时候。我们从我们存储的txt文件中读取所有的JSON语句并转化成实体类存入字典中,并刷新填充datagridview。
将方法写入窗口的Load方法中。
string path = Application.StartupPath;//获取程序路径
private void MainForm_Load(object sender, EventArgs e)
{
if (File.Exists($"{path}/config/MainSet.txt"))//查找这个文件是否存在
{
string[] lines = File.ReadAllLines($"{path}/config/MainSet.txt");//一行行写入一个字符串数组
for (int i = 0; i < lines.Length; i++)
{
Client a = JsonConvert.DeserializeObject<Client>(lines[i]);//每一句都解析成类保存
string Id = a.id;
if (!db.ContainsKey(Id))
{
db.Add(Id, a);
}
}
}
DataBind();//填充列表
}
本地存储与读取效果图,成功保留表数据
JSON的txt文件展示,数据就是以这种形式保存下来的(跟侠客风云传前传的存档文件一毛一样,有兴趣的可以去看看)。
结语
好了!到这里已经全部结束了,还望大家多多指教,能帮到大家那是最好的!
你已经是一个脱离数据库的特殊crud选手了!!快来做一个多线程Socket连接程序吧!(不是)
这是博主的项目(- ▽ -)给你们露两手!