自己实现的一个简单的EF框架(反射实现)

时间:2022-03-10 21:32:09

我实现了一个简单的EF框架,主要用于操纵数据库。实现了对数据库的基本操纵--CRUD

这是项目结构 这是一个 core 下的 DLL

自己实现的一个简单的EF框架(反射实现)

写了一个数据库工厂,用于执行sql语句。调用sql语句工厂

写了一个sql语句工厂,用于生成sql语句。调用类型工厂

写了一个类型工厂,用于获取所需的类型,识别特性等。

appsettings.json是配置文件

最后一个是使用说明

我实现过程的首先从底层开始。

首先写的是类型工厂

结构

自己实现的一个简单的EF框架(反射实现)

BaseTypeHelper.cs 是基础的类型帮助类

TypeHelperFactory.cs是一个工厂,调用BaseTypeHelper.cs,是现自己的功能,同时为sql语句工厂提供服务。

首先介绍一下BaseTypeHelper.cs类。这是代码。为TypeHelperFactory提供了必要的服务

  public static class BaseTypeHelper
{
#region 获取单个成员
private static MemberInfo GetOneMember(Type t, string MemberName)
{
return GetAllMembers(t).FirstOrDefault(m => m.Name == MemberName);
} #endregion #region 获取所有成员
public static MemberInfo[] GetAllMembers(Type t)
{
return t.GetMembers();
} #endregion #region 获取成员的属性 /// <summary>
/// 获取成员的属性
/// </summary>
/// <param name="obj">目标类</param>
/// <param name="MemberName">成员名称</param>
/// <returns></returns>
private static PropertyInfo GetProperty(object obj, string MemberName)
{
var type = obj.GetType();
var member = GetOneMember(type, MemberName);
return type.GetProperty(member.Name); }
#endregion #region 执行法并返回结果
/// <summary>
/// 获取方法的返回值
/// </summary>
/// <param name="MethodName">方法的名称</param>
/// <param name="instance">实例</param>
/// <param name="param">参数列表,如果没有参数则置为null</param>
/// <returns></returns>
public static object GetMethodValue(string MethodName, object instance, params object[] param)
{
Type t = instance.GetType();
try
{
MethodInfo info = t.GetMethod(MethodName);
return info.Invoke(instance, param);
}
catch (Exception e)
{
Console.WriteLine("方法没有找到," + e);
throw;
} }
#endregion #region 获取声明成员的类型 /// <summary>
/// 获取声明成员的类型
/// 说明:返回若为空则 没有找到
/// 若不为空,则查找正常
/// </summary>
/// <param name="MemberName">成员的名称</param>
/// <param name="t">所在类的类型</param>
/// <returns></returns>
public static string GetPropertyType(string MemberName, Type t)
{
MemberInfo member = GetOneMember(t, MemberName);
if (member != null)
{
PropertyInfo property = t.GetProperty(member.Name);
return property.PropertyType.Name;
}
return null; }
#endregion #region 获取单个成员是否含有某个属性 /// <summary>
/// 获取单个成员是否含有某个特性
/// </summary>
/// <param name="MemberName">成员的名称</param>
/// <param name="t">所在类的类型</param>
/// <param name="attribute">要获取的特性</param>
/// <returns></returns>
public static bool CustomAttributeExist(string MemberName, Type t, Attribute attribute)
{ var Member = GetOneMember(t, MemberName);
var My_customAttribute = Member.CustomAttributes.FirstOrDefault(
a => a.AttributeType == attribute.GetType());
return My_customAttribute != null;
}
#endregion #region 通过SetValue给成员设值 /// <summary>
/// 给成员设值
/// </summary>
/// <param name="obj">目标类</param>
/// <param name="MemberName">类内属性名称</param>
/// <param name="value">设置的值</param>
public static void SetValue(object obj, string MemberName, object value)
{
var Property = GetProperty(obj, MemberName);
Property.SetValue(obj, value);
}
#endregion #region 通过GetValue给成员取值 /// <summary>
/// 取成员的值
/// </summary>
/// <param name="obj">目标类</param>
/// <param name="MemberName">成员的名称</param>
/// <returns></returns>
public static object GetValue(object obj, string MemberName)
{
var Property = GetProperty(obj, MemberName);
return Property.GetValue(obj);
}
#endregion }

BaseTypeHelper.cs

这是TypeHelperFactory.cs 这是一个工厂,利用BaseTypeHelper.cs 提供的服务的基础之上为上层,提供这么几个功能,获取所有的属性列表,获取属性和值的字典集,获取主键(实现了特性[key]的识别,以及默认的ID.toLower()),获取表名(这里获取表名的时候,只是简单的实现了获取类的名称,并没有实现获取特性[Table(Name="...")]这个功能,会在以后更新上去的),获取主键的名称,获取主键的值,获取声明主键的类型,给属性设值。

     public static class TypeHelperFactory
{
#region GetAllPropertyList public static List<string> GetAllPropertyList(Type type)
{
var Propertys = BaseTypeHelper
.GetAllMembers(type)
.ToList()
.FindAll(member => member.MemberType == MemberTypes.Property);
var PropertyList = new List<string>();
foreach (var item in Propertys)
{
PropertyList.Add(item.Name);
}
return PropertyList;
} #endregion #region GetAllPropertyNameAndValueDictionary
//添加到字典中的时候已经去除掉空的值
public static Dictionary<string, object> GetAllPropertyNameAndValueDictionary(object obj)
{
Type type = obj.GetType();
var PropertyList = GetAllPropertyList(type);
var PropertyValueList = new Dictionary<string, object>();
foreach (var Property in PropertyList)
{
var value = BaseTypeHelper.GetValue(obj, Property);
if (value == null) continue;
PropertyValueList.Add(Property, value);
}
return PropertyValueList;
} #endregion #region GetTableName
/// <summary>
///简单获取类的名称
/// 未查找特性[table(Name="")]的标注
/// 2017-5-9 18:00
/// Author :曲
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string GetTableName(Type type)
{
return type.Name;
} #endregion #region GetPrimaryKey public static string GetPrimaryKey(Type type)
{
//1.查找特性标注为key的
//2.如果不存在 查找 类型为int和名称为ID/Id/id 的
var memberNameList = GetAllPropertyList(type);
var attrribute = new KeyAttribute(); foreach (var item in memberNameList)
{
if (BaseTypeHelper.CustomAttributeExist(item, type, attrribute))
{
return item;
}
}
return memberNameList.FirstOrDefault(
key => key.ToLower() == "id"
&& BaseTypeHelper
.GetPropertyType(key, type)
.Contains("Int")
); } #endregion #region GetPrimaryKeyValue public static object GetPrimaryKeyValue(object obj, string PrimaryKeyName)
{
return BaseTypeHelper.GetValue(obj, PrimaryKeyName);
} #endregion #region GetPrimaryKeyType public static string GetPrimaryKeyType(Type type, string PrimaryKey)
{
return BaseTypeHelper.GetPropertyType(PrimaryKey, type);
} #endregion #region SetPropertyValue public static void SetPropertyValue(object obj, string MemberName, object value)
{
BaseTypeHelper.SetValue(obj, MemberName, value);
} #endregion
}

TypeHelperFactory.cs

接下来写的是sql语句构造工厂,这是工厂结构。

自己实现的一个简单的EF框架(反射实现)

两个文件夹,一个实现接口文件夹,一个接口文件夹。两个类,一个枚举类,一个对外提供服务的类,也就是外界只能通过这一个类,获取我这一层对外提供的服务。

首先介绍一下接口文件夹 :里边定义了接口的类型,以及方法

里边有五个接口,分别是ICreate、IDelete、IRead、IUpdate、ISqlStatementBuilder。

对这五个接口进行说明一下,ISqlStatementBuilder是总的接口,即对外提供服务的接口,分别继承了CURD这四个接口。

    public interface ISqlStatementBuilder : ICreate, IRead, IUpdate, IDelete
{ }

ISqlStatementBuilder.cs

     public interface ICreate
{
string CreateSqlString(object obj);
}

ICreate.cs

     public interface IDelete
{
string DeleteSqlString(object obj); }

IDelete.cs

     public interface IRead
{
string ReadSqlString(object obj);
}

IRead.cs

     public interface IUpdate
{
string UpdateSqlString(object obj);
}

IUpdate.cs

接下来是:实现接口文件夹,这个文件夹包含了三类数据库的对应的sql不同的sql语句。MySql,Oracle,SqlServer,这里我只实现了sqlserver对应的数据库的sql语句。

自己实现的一个简单的EF框架(反射实现)

    public class SqlServerSqlStatement :ISqlStatementBuilder
{ #region 获取Insertsql语句 实现ICreate接口 public string CreateSqlString(object obj)
{
var type = obj.GetType();
var TableName = TypeHelperFactory.GetTableName(type);
var PrimaryKeyName = TypeHelperFactory.GetPrimaryKey(type);
var PropertyNameAndValueDictionary = TypeHelperFactory.GetAllPropertyNameAndValueDictionary(obj);
PropertyNameAndValueDictionary.Remove(PrimaryKeyName);
//这里进行了修改 2017-5-17 17:10
//这是原来的代码
//var PropertyNameList = new List<string>();
//var PropertyValueList = new List<object>();
//foreach (var item in PropertyNameAndValueDictionary)
//{
// PropertyNameList.Add(item.Key);
// PropertyValueList.Add(item.Value);
//}
//这是新的代码 测试已通过 以后考虑用元组实现 2017-5-17 17:15
var PropertyNameList = from item in PropertyNameAndValueDictionary
select item.Key;
var PropertyValueList = from item in PropertyNameAndValueDictionary
select item.Value;
string sql1 = string.Join(",", PropertyNameList);
string sql2 = "'";
sql2 += string.Join("','", PropertyValueList);
sql2 += "'";
var SqlStatement = new StringBuilder();
SqlStatement.AppendFormat($"insert into {TableName} ({sql1}) values ({sql2})");
return SqlStatement.ToString(); } #endregion #region 获取Readsql语句 实现IRead接口 public string ReadSqlString(object obj)
{
var type = obj.GetType();
var PropertyList = TypeHelperFactory.GetAllPropertyList(type);
var TableName = TypeHelperFactory.GetTableName(type);
string SelectString = string.Join(",", PropertyList);
StringBuilder SqlStatement = new StringBuilder();
SqlStatement.AppendFormat($"select {SelectString} from {TableName}");
return SqlStatement.ToString();
} #endregion #region 获取Updatesql语句 实现IUpdate接口
public string UpdateSqlString(object obj)
{
var type = obj.GetType();
var TableName = TypeHelperFactory.GetTableName(type);
var PrimaryKeyName = TypeHelperFactory.GetPrimaryKey(type);
if (PrimaryKeyName == null)
{
throw new Exception("不存在主键");
}
var PrimaryKeyValue = TypeHelperFactory.GetPrimaryKeyValue(obj, PrimaryKeyName);
var PropertyNameAndValueDictionary = TypeHelperFactory.GetAllPropertyNameAndValueDictionary(obj);
PropertyNameAndValueDictionary.Remove(PrimaryKeyName);
//这里进行了修改 2017-5-17 17:00
//这是原来的代码
//var NameAndValueList = new List<string>();
//foreach (var item in PropertyNameAndValueDictionary)
//{
// NameAndValueList.Add($"{item.Key}='{item.Value}'");
//} //这是新的代码 2017-5-17 17:00 测试已通过
var NameAndValueList = from item in PropertyNameAndValueDictionary
select $"{item.Key}='{item.Value}'";
string sql = string.Join(",", NameAndValueList);
StringBuilder sqlStatement = new StringBuilder();
sqlStatement.AppendFormat(
$"update {TableName} set {sql} " +
$"where {PrimaryKeyName}='{PrimaryKeyValue}'"
);
return sqlStatement.ToString();
} #endregion #region 获取Deletesql语句 实现IDelete接口
public string DeleteSqlString(object obj)
{
var type = obj.GetType();
var TableName = TypeHelperFactory.GetTableName(type);
var PrimaryKey = TypeHelperFactory.GetPrimaryKey(type);
if (PrimaryKey == null)
{
throw new Exception("不存在主键");
}
var PrimaryKeyValue = TypeHelperFactory.GetPrimaryKeyValue(obj, PrimaryKey);
StringBuilder SqlStatement = new StringBuilder();
SqlStatement.AppendFormat($"delete from {TableName} where {PrimaryKey}='{PrimaryKeyValue}'");
return SqlStatement.ToString();
} #endregion
}

SqlServerSqlStatement.cs

这个类实现了ISqlStatementBuilder这个接口里边定义的方法。其余的两个类只是继承了接口,并没有实现接口定义的具体方法。

接下来这一层的重点来了!

首先这是一个数据库的枚举类

   /// <summary>
/// 这是一个枚举类型 用于枚举数据库的类型
/// </summary>
public enum DataBaseType
{
SqlServer = ,
MySql = ,
Oracle =
}

DataBaseType.cs

接下来这是对外提供服务的一个类,我用反射获取枚举过来的数据库的类型字符串,然后通过反射生成对应的实例。(这个东西的详细用法,我的这篇博客里有介绍http://www.cnblogs.com/qulianqing/p/6842829.html

     public static class SqlBuilderFactory
{
public static ISqlStatementBuilder GetInstance(DataBaseType DbType)
{
string DataBaseTypeName = Enum.Parse(DbType.GetType(), DbType.ToString()).ToString();
var NamespaceName = "MyEntityFrameWork.SqlBuilderFactorys.Implement";
string InstanceClassName = DataBaseTypeName + "SqlStatement";
return (ISqlStatementBuilder)Assembly.Load(new AssemblyName("MyEntityFrameWork"))
.CreateInstance(NamespaceName+"."+InstanceClassName);
} }

对于外界调用只用这样声明里边参数只用传递对应的数据库的类型。

ISqlStatementBulider  SqlBuilder = SqlBuilderFactory.GetInstance(DataBaseType.SqlServer);

这样做的好处是:对于拓展我是支持的,对于外界调用者,他并不知道我是怎么实现的,我用什么方式实现的,以及怎么使用。他只需改变传递的枚举参数就可以了。

如果说以后加入了Access类型的数据库,我只用在枚举类中加入Access的枚举,在实现文件夹中添加AccessSqlStatement.cs这样的一个类,让它实现ISqlSatementBulider接口定义的方法,这样就完成了。这样做我个人感觉拓展很容易。具体是什么设计模式,我也忘了,反正就是这个思想。

接下来我们进入了第三层,也就是目前来说的最高层,也是算是应用层。

这一层是 一个数据库工厂,用于执行sql语句。调用sql语句的工厂

这是这一层的结构,首先是一个基类文件夹,一个实现文件夹,一个接口文件夹(目前为空尚未使用)

自己实现的一个简单的EF框架(反射实现)

在基类文件夹中声明了一个抽象类BasicsDatabase.cs这是一个基类,定义了需要实现的方法。(我仿照着.net core 下的asp.net mvc 项目里的StartUp.cs的构造函数,实现了一个我自己用的一个构造函数。读取json配置文件必须用的一个IConfigurationRoot )

     public abstract class BasicsDatabase
{
protected ISqlStatementBuilder SqlBuilder { get; set; }
protected IDbConnection Connection { get; set; }
public IConfigurationRoot Configuration { get; }
protected IDbCommand Command { get; set; }
protected BasicsDatabase()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build();
} protected abstract string DatabaseConncetionString();
public abstract List<T> GetAllInfo<T>() where T : new();
public abstract bool Add(object data);
public abstract bool Update(object data);
public abstract bool Remove(object data);
}

BasicsDatabase.cs

接下来是实现文件夹

自己实现的一个简单的EF框架(反射实现)

分别实现了三种不同的数据库,我这里只是实现了SqlServer的数据库类型。其他两个只是演示了怎么使用,以及怎么实现。

     public class SqlServerDatabase : BasicsDatabase
{
public SqlServerDatabase() : base()
{
base.Connection = new SqlConnection(DatabaseConncetionString());
Connection.Open();
base.Command = new SqlCommand();
Command.Connection = base.Connection;
//这里重要
base.SqlBuilder = SqlBuilderFactory.GetInstance(DataBaseType.SqlServer);
} public override List<T> GetAllInfo<T>()
{
Command.CommandText = SqlBuilder.ReadSqlString(new T());
var Result = Command.ExecuteReader(CommandBehavior.SingleResult);
var AllInfoList = new List<T>();
while (Result.Read())
{
var Record = (IDataRecord)Result;
AddOneObjectToList(ref AllInfoList, Record);
} Result.Dispose();
return AllInfoList;
} private void AddOneObjectToList<T>(ref List<T> objectList, IDataRecord record) where T : new()
{
//获取所有的特性名称
//查找record中的字段名称是否相同
//如果相同将其值赋给该字段
var PropertyNames = TypeHelperFactory.GetAllPropertyList(typeof(T));
var Obj = new T();
for (int i = ; i < record.FieldCount; i++)
{
var PropertyName = PropertyNames.FirstOrDefault(name => name == record.GetName(i));
if (PropertyName != null)
{
TypeHelperFactory.SetPropertyValue(Obj, PropertyName, record[i]);
} }
objectList.Add(Obj);
}
public override bool Add(object data)
{
Command.CommandText = SqlBuilder.CreateSqlString(data);
return Command.ExecuteNonQuery() > ;
}
public override bool Remove(object data)
{
Command.CommandText = SqlBuilder.DeleteSqlString(data);
return Command.ExecuteNonQuery() > ;
} public override bool Update(object data)
{
Command.CommandText = SqlBuilder.UpdateSqlString(data);
return Command.ExecuteNonQuery() > ;
} protected override string DatabaseConncetionString()
{
return base.Configuration.GetConnectionString("DataContext");
} }

SqlServerDatabase.cs

重点代码已经在注释中标注。这里给大家提一个醒在 24行的代码出,如果用.close()这个方法的话,add,和Update方法都没有问题,但是在Delete方法执行的时候会抛IDataReader没有关闭,请关闭后再使用的异常,用.dispose()这个方法可以解决。这是微软官网给的IDataReader样例https://docs.microsoft.com/en-us/dotnet/api/system.data.idatareader?view=netcore-1.1他用的是Close()这个方法,我刚开始也是用的是close(),这个方法,后来找了好久,改为dispose()就好了,具体原因还没有深入了解。

这是其余两个只是演示了怎么使用

     public class MySqlDataBase : BasicsDatabase
{
/// <summary>
/// 这是MySql数据库的实现方式
/// 只用于演示并没有实现功能
/// 时间:2017-5-12
/// Author:曲
/// </summary>
public MySqlDataBase() : base()
{
base.Connection = new MySqlConnection();
base.Connection.Open();
base.Command = new MySqlCommand();
base.Command.Connection = base.Connection;
base.SqlBuilder = SqlBuilderFactory.GetInstance(DataBaseType.MySql);
}
public override bool Add(object data)
{
base.Command.CommandText = SqlBuilder.CreateSqlString(data);
return base.Command.ExecuteNonQuery() > ;
} public override List<T> GetAllInfo<T>()
{
throw new NotImplementedException();
} public override bool Remove(object data)
{
throw new NotImplementedException();
} public override bool Update(object data)
{
throw new NotImplementedException();
} protected override string DatabaseConncetionString()
{
throw new NotImplementedException();
}
}

MySqlDataBase.cs

     /// <summary>
/// Oracle数据库的实现方式
/// 这是只是用于演示并没有真正实现
/// 时间:2017-5-12
/// Author:曲
/// </summary>
public class OracleDataBase : BasicsDatabase
{ public OracleDataBase() : base()
{
//base.Connection = new OracleConnection(DatabaseConncetionString());
//Connection.Open();
//base.Command = new OracleCommmand();
//Command.Connection = Connection;
//base.SqlBuilder = SqlBuilderFactory.GetInstance(DataBaseType.Oracle);
}
public override bool Add(object data)
{
Command.CommandText = SqlBuilder.CreateSqlString(data);
return Command.ExecuteNonQuery() > ;
} public override List<T> GetAllInfo<T>()
{
throw new NotImplementedException();
} public override bool Remove(object data)
{
throw new NotImplementedException();
} public override bool Update(object data)
{
throw new NotImplementedException();
} protected override string DatabaseConncetionString()
{
throw new NotImplementedException();
}
}

OracleDataBase.cs

这里还是挺丑的毕竟每个子类都要知道具体的实现步骤,准备把子类构造函数里的构造过程,放到抽象类中去,不知道能不能行的通,待明天尝试

到了这里我的EF框架大致就模拟成功了,可以实现CRUD到数据库中。

关于使用,我写了一个  使用说明.md文件。上边有配置步骤,可以一步步的配置然后就可以使用了。

配置我自己定义的服务(这里实现的是sqlServer类型的数据库,如果实现了其他类型的,在这里改动一下就行了)

   //添加我自己的服务,这里声明的类型为基类型性,实现的是sqlServer的的数据库的类型。
services.AddScoped<BasicsDatabase, SqlServerDatabase>();

我在core环境下建了一个mvc项目

自己实现的一个简单的EF框架(反射实现)

在views文件夹中添加一个UserInfo文件夹

自己实现的一个简单的EF框架(反射实现)

里边有这么几个文件和用EF框架生成的一样,不过这里要进行手工的写,

在Controller文件夹中添加一个mvc控制器 在控制器中是是这么用的

 using Microsoft.AspNetCore.Mvc;
using MyEntityFrameWork.DateBaseFactory.BaseClass;
using System;
using System.Linq;
using TestMyEntityFramework.Models; // For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 namespace TestMyEntityFramework.Controllers
{
public class UserInfoController : Controller
{
private readonly BasicsDatabase _context;
public UserInfoController(BasicsDatabase Database)
{
_context = Database;
} // GET: /<controller>/
public IActionResult Index()
{ return View(_context.GetAllInfo<Users>());
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("ID,Name,pwd")] Users userInfo)
{
if (ModelState.IsValid)
{
_context.Add(userInfo);
return RedirectToAction("Index");
}
return View(userInfo);
}
public IActionResult Edit(int? id)
{
if (id == null)
{
return NotFound();
} var userInfo = _context.GetAllInfo<Users>().FirstOrDefault(u=>u.ID==id);
if (userInfo == null)
{
return NotFound();
}
return View(userInfo);
} [HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, [Bind("ID,Name,pwd")] Users userInfo)
{
if (id != userInfo.ID)
{
return NotFound();
} if (ModelState.IsValid)
{
try
{
_context.Update(userInfo); }
catch
{
if (!UserInfoExists(userInfo.ID))
{
return NotFound();
}
else
{
throw new Exception("数据更新失败");
}
}
return RedirectToAction("Index");
}
return View(userInfo);
} public IActionResult Delete(int? id)
{
if (id == null)
{
return NotFound();
} var userInfo = _context.GetAllInfo<Users>()
.SingleOrDefault(m => m.ID == id);
if (userInfo == null)
{
return NotFound();
} return View(userInfo);
} [HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(int id)
{
var userInfo = _context.GetAllInfo<Users>().SingleOrDefault(m => m.ID == id); _context.Remove(userInfo);
return RedirectToAction("Index");
} public IActionResult Details(int? id)
{
if (id == null)
{
return NotFound();
} var userInfo = _context.GetAllInfo<Users>()
.SingleOrDefault(m => m.ID == id);
if (userInfo == null)
{
return NotFound();
} return View(userInfo);
}
private bool UserInfoExists(int id)
{
return _context.GetAllInfo<Users>().Any(e => e.ID == id);
}
}
}

UserInfoController.cs

是不是和EF挺像的,就是GetAllList<T>这个方法有点丑,待我想到好方法,再改进一下,据说EF不是用反射,用的是泛型,我今天大致看了一下源码,确实是这样的,挺好的应该,毕竟反射对性能损失是较大的。

待我研究出来头绪了,再和大家分享。

git地址:https://github.com/1483523635/MyEntityFramework

测试的Git地址:https://github.com/1483523635/UseMyEntityFrameWork