-
让ADO.NET Entity Framework 支持ACCESS数据库
如写的不好请见谅,本人水平有限。
个人简历及水平:。 http://www.cnblogs.com/hackdragon/p/3662599.html
接到一个程序和网页交互的项目,用ADO.NET Entity Framework (以下简称EF)很快就搞完了,但是对方的空间提供的MSSQL数据库比较昂贵,所以就采用ACCESS数据库,但是我查了资料发现 EF不支持Access数据库,(以前觉得LINQ TO SQL 不支持 这个应该支持),写完的代码不想用OLEDB在写了,于是网上一顿查,试验了ALINQ和其他很多的,总是不能符合项目的需要。不是更新不行就算插入失败,要不就是经常查询错误。最后没办法,我自己决定写一个实体支持ACCESS数据库,我觉得懒人应该有需要这个的,当然大侠们估计有更好的办法来更懒一些。
懒人第一步:
因为VS的实体生成器不支持ACCESS数据库,所以无法生成代码,但是想快速开发项目,那么你可以用SQL数据库先来设计,然后导出数据库到ACCESS数据库,这样做的目的是让生成器可以生成我们所需要的代码。注意设计数据库字段的时候要考虑他们的兼容性。
勤快第二步:
开始写代码了,原始的上下文代码如下:
#region 上下文 /// <summary> /// 没有元数据文档可用。 /// </summary> public partial class SqlDoorEntities1 : ObjectContext { #region 构造函数 /// <summary> /// 请使用应用程序配置文件的“SqlDoorEntities1”部分中的连接字符串初始化新 SqlDoorEntities1 对象。 /// </summary> public SqlDoorEntities1() : base("name=SqlDoorEntities1", "SqlDoorEntities1") { OnContextCreated(); } /// <summary> /// 初始化新的 SqlDoorEntities1 对象。 /// </summary> public SqlDoorEntities1(string connectionString) : base(connectionString, "SqlDoorEntities1") { OnContextCreated(); } /// <summary> /// 初始化新的 SqlDoorEntities1 对象。 /// </summary> public SqlDoorEntities1(EntityConnection connection) : base(connection, "SqlDoorEntities1") { OnContextCreated(); } #endregion #region 分部方法 partial void OnContextCreated(); #endregion #region ObjectSet 属性 /// <summary> /// 没有元数据文档可用。 /// </summary> public ObjectSet<CmdMsg> CmdMsg { get { if ((_CmdMsg == null)) { _CmdMsg = base.CreateObjectSet<CmdMsg>("CmdMsg"); } return _CmdMsg; } } private ObjectSet<CmdMsg> _CmdMsg; /// <summary> /// 没有元数据文档可用。 /// </summary> public ObjectSet<Door> Door { get { if ((_Door == null)) { _Door = base.CreateObjectSet<Door>("Door"); } return _Door; } } private ObjectSet<Door> _Door; /// <summary> /// 没有元数据文档可用。 /// </summary> public ObjectSet<Manager> Manager { get { if ((_Manager == null)) { _Manager = base.CreateObjectSet<Manager>("Manager"); } return _Manager; } } private ObjectSet<Manager> _Manager; /// <summary> /// 没有元数据文档可用。 /// </summary> public ObjectSet<Users> Users { get { if ((_Users == null)) { _Users = base.CreateObjectSet<Users>("Users"); } return _Users; } } private ObjectSet<Users> _Users; #endregion #region AddTo 方法 /// <summary> /// 用于向 CmdMsg EntitySet 添加新对象的方法,已弃用。请考虑改用关联的 ObjectSet<T> 属性的 .Add 方法。 /// </summary> public void AddToCmdMsg(CmdMsg cmdMsg) { base.AddObject("CmdMsg", cmdMsg); } /// <summary> /// 用于向 Door EntitySet 添加新对象的方法,已弃用。请考虑改用关联的 ObjectSet<T> 属性的 .Add 方法。 /// </summary> public void AddToDoor(Door door) { base.AddObject("Door", door); } /// <summary> /// 用于向 Manager EntitySet 添加新对象的方法,已弃用。请考虑改用关联的 ObjectSet<T> 属性的 .Add 方法。 /// </summary> public void AddToManager(Manager manager) { base.AddObject("Manager", manager); } /// <summary> /// 用于向 Users EntitySet 添加新对象的方法,已弃用。请考虑改用关联的 ObjectSet<T> 属性的 .Add 方法。 /// </summary> public void AddToUsers(Users users) { base.AddObject("Users", users); } #endregion } #endregion
ObjectContext 继承于IDisposable 那么我写一个自己的 ObjectContext 这样的类 我给他起个名字叫EFToAccess 那么多 构造方法 我们就需要2个一个 是 给定的连接字符串 一个是默认从webconfig中读取的链接字符串就可以了。本人偷懒,直接读取指定的路径了。数据库的简单读写可能都依赖一个where查询,那么怎么实现自己的where查询就很关键,于是我看资料研究了2天Lambda Expression 表达式。最后还是看了 博客园的一篇 扩展LINQ to SQL:使用Lambda Expression批量删除数据才会用,现在也不是很明白,懒人就是拿来主义,不怎么消化,我现在也没多少时间消化知识,估计这样的人也不少吧。下面是我自己用的的方法,利用VS生成的代码 2个替换1个删除搞定 (ObjectContext替换“你自己的类名我的是SqlDoorEntities”,ObjectSet替换成IEnumerable,删除无用的构造函数)
public class SqlDoorEntities : EFToAccess { public SqlDoorEntities():base("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +AppDomain.CurrentDomain.BaseDirectory + "bin\\DataDoor.mdb")
{ } #region IEnumerable 属性 /// <summary> /// 没有元数据文档可用。 /// </summary> public IEnumerable<CmdMsg> CmdMsg { get { if ((_CmdMsg == null)) { _CmdMsg = base.CreateObjectSet<CmdMsg>("CmdMsg"); } return _CmdMsg; } } private IEnumerable<CmdMsg> _CmdMsg; /// <summary> /// 没有元数据文档可用。 /// </summary> public IEnumerable<Door> Door { get { if ((_Door == null)) { _Door = base.CreateObjectSet<Door>("Door"); } return _Door; } } private IEnumerable<Door> _Door; /// <summary> /// 没有元数据文档可用。 /// </summary> public IEnumerable<Manager> Manager { get { if ((_Manager == null)) { _Manager = base.CreateObjectSet<Manager>("Manager"); } return _Manager; } } private IEnumerable<Manager> _Manager; /// <summary> /// 没有元数据文档可用。 /// </summary> public IEnumerable<Users> Users { get { if ((_Users == null)) { _Users = base.CreateObjectSet<Users>("Users"); } return _Users; } } private IEnumerable<Users> _Users; #endregion #region AddTo 方法 /// <summary> /// 用于向 CmdMsg EntitySet 添加新对象的方法,已弃用。请考虑改用关联的 IEnumerable<T> 属性的 .Add 方法。 /// </summary> public void AddToCmdMsg(CmdMsg cmdMsg) { base.AddObject("CmdMsg", cmdMsg); } /// <summary> /// 用于向 Door EntitySet 添加新对象的方法,已弃用。请考虑改用关联的 IEnumerable<T> 属性的 .Add 方法。 /// </summary> public void AddToDoor(Door door) { base.AddObject("Door", door); } /// <summary> /// 用于向 Manager EntitySet 添加新对象的方法,已弃用。请考虑改用关联的 IEnumerable<T> 属性的 .Add 方法。 /// </summary> public void AddToManager(Manager manager) { base.AddObject("Manager", manager); } /// <summary> /// 用于向 Users EntitySet 添加新对象的方法,已弃用。请考虑改用关联的 IEnumerable<T> 属性的 .Add 方法。 /// </summary> public void AddToUsers(Users users) { base.AddObject("Users", users); } #endregion }
懒人第三步:
为了让代码和EF使用方法基本一致,所以不得不做一些工作让我写的类基本满足项目需要。首先实现一个让Lambda Expression 表达式变成字符串的函数
string GetWhereString(Expression Func) { ConditionBuilder conditionBuilder = new ConditionBuilder(); conditionBuilder.Build(Func); for (int i = 0; i < conditionBuilder.Arguments.Length; i++) { object ce = conditionBuilder.Arguments[i]; if (ce == null) conditionBuilder.Arguments[i] = "null"; else if (ce is ValueType) conditionBuilder.Arguments[i] = ce.ToString(); else if (ce is string || ce is char) conditionBuilder.Arguments[i] = string.Format("'{0}'", ce.ToString()); else if (ce is DateTime) conditionBuilder.Arguments[i] = string.Format("#{0}#", ce.ToString()); } return string.Format(conditionBuilder.Condition, conditionBuilder.Arguments); }
上面的ConditionBuilder类代码我就不贴出了。大家参考我提到的那篇文章,如果实际应用当中,有死循环的地方那么应该在该调用基类的地方加入base.XXX比如base.Visit
实现我们自己的where 这里如果我们自己生成类 那么我们的实体类类可以直接有一个where 方法,但是我为了能让我们懒人使用VS生成的实体类只能这么实现了。为了代码利用率,我们还需要另外2个函数。
取得所有记录
IEnumerable<TEntity> SelectAll<TEntity>() where TEntity : new() { TEntity TDefault = new TEntity(); string entitySetName = TDefault.GetType().Name; string strSqlQuery = string.Format("SELECT * FROM {0}", entitySetName); m_LastSqlCommand = strSqlQuery; return SelectWhere<TEntity>(strSqlQuery); }
经常的条件查询
public IEnumerable<TEntity> Where<TEntity>(Expression<Func<TEntity, bool>> Func) where TEntity : new() { TEntity TDefault = new TEntity(); string entitySetName = TDefault.GetType().Name; string strWhere = GetWhereString(Func).Replace("Where", entitySetName); string strSqlQuery = string.Format("SELECT * FROM {0} WHERE {1} ", entitySetName, strWhere); m_LastSqlCommand = strSqlQuery; return SelectWhere<TEntity>(strSqlQuery); }
最后的where
IEnumerable<TEntity> SelectWhere<TEntity>(string strSqlQuery) where TEntity : new() { TEntity TDefault = new TEntity(); //确认基础类型是否是 EntityObject类型 Type TBase = TDefault.GetType(); while ((TBase.BaseType) != null) { if (TBase.Name == "EntityObject") break; TBase = TBase.BaseType; } bool IsPCEH = false; if (TBase != null && TBase.Name == "EntityObject") IsPCEH = true; PropertyInfo[] properties = TDefault.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); List<TEntity> Records = new List<TEntity>(); string entitySetName = TDefault.GetType().Name; try { OleDbCommand Cmd = new OleDbCommand(strSqlQuery, AccessConn); OleDbDataReader sqlReader = Cmd.ExecuteReader(); #region 数据库查询开始 while (sqlReader.Read()) { TEntity TValue = new TEntity(); //输入是EntityObject类型,那么加入属性改变事件 if (IsPCEH) { EntityObject EO = TValue as EntityObject; EO.PropertyChanged += PropertyChangedEH; } for (int i = 0; i < sqlReader.FieldCount; i++) { string strField = sqlReader.GetName(i); //根据字段名 反射 类的属性 PropertyInfo p = properties.Where(P => string.Compare(P.Name, strField, true) == 0).First(); #region 数据转换 switch (p.PropertyType.Name.ToString().ToLower()) { case "int16": if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetInt16(i), null); break; case "int32": if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetInt32(i), null); break; case "int64": if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetInt64(i), null); break; case "string": if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetString(i), null); break; case "double": if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetDouble(i), null); break; case "float": if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetFloat(i), null); break; case "decimal": if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetDecimal(i), null); break; case "datetime": if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetDateTime(i), null); break; default: if (!sqlReader.IsDBNull(i)) p.SetValue(TValue, sqlReader.GetValue(i), null); break; } #endregion } Records.Add(TValue); } #endregion Cmd.Clone(); m_IsDetectionChange = true; } catch (Exception) { throw; } return Records; }
属性的改变(也就是数据库记录的字段值)我们要知道才能实现EF的SaveChanges()函数那么我们需要简历一个数组变量,直接上代码
//表名 //主键 //属性 值1 值2 主键类型名字 Dictionary<string, Dictionary<string, Dictionary<string, object[]>>> m_ArrDetection = new Dictionary<string, Dictionary<string, Dictionary<string, object[]>>>();
void PropertyChangedEH(object sender, PropertyChangedEventArgs e) { //没有开启返回 if (!m_IsDetectionChange) return; //反射所有属性 PropertyInfo[] properties = sender.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); //查询主键 PropertyInfo EntityKey = GetEntityKeyFormAllProperty(properties); //没有主键的返回(表必须要有主键) if (EntityKey == null) return; //表名字 string Table = sender.GetType().Name; //主键值 string MainKey = EntityKey.GetValue(sender, null).ToString(); if (MainKey == null || MainKey == "") return; //没有表 就添加 if (!m_ArrDetection.ContainsKey(Table)) m_ArrDetection[Table] = new Dictionary<string, Dictionary<string, object[]>>(); //没有主键 就添加 if (!m_ArrDetection[Table].ContainsKey(MainKey)) m_ArrDetection[Table][MainKey] = new Dictionary<string, object[]>(); //主键是不用更新的(一般数据库主键都是自动增长的吧,尤其快速开发的项目) if (e.PropertyName == MainKey) return; PropertyInfo p = properties.Where(P => string.Compare(P.Name, e.PropertyName, true) == 0).First(); //赋值 m_ArrDetection[Table][MainKey][e.PropertyName] = new object[2] { p.GetValue(sender, null), EntityKey.Name }; }
查找主键的函数
private bool IsEntityKeyProperty(PropertyInfo Info) { foreach (Attribute attr in Attribute.GetCustomAttributes(Info)) { if (attr is System.Data.Objects.DataClasses.EdmScalarPropertyAttribute) { System.Data.Objects.DataClasses.EdmScalarPropertyAttribute Key = (System.Data.Objects.DataClasses.EdmScalarPropertyAttribute)attr; if (Key.EntityKeyProperty == true) { return true; } } } return false; } private PropertyInfo GetEntityKeyFormAllProperty(PropertyInfo[] properties) { foreach (PropertyInfo Info in properties) { if (IsEntityKeyProperty(Info)) return Info; } return null