Revit:二开使用Sqlite保存本地数据,并配合EF6等ORM框架

前人种树,后人乘凉。

在Revit中如何使用Sqlite是一项非常艰难的事情。

这也是我写在这里的感言,前后花了满满2个工作日,搜遍全网零碎的代码,最终才拼出解决方案。
你想要直接的解决方案,绝对没有,一定是自己摸索解决的。如果你哪天看到有了,那么你看到的文章极有可能是我写的,或者别人转载了我的文章。

使用原生的System.Data.Common调用SQlite是一件轻松简单的事情:

using (var conn = new SQLiteConnection(@"data source=E:公司项目RevitDevelopmentinDebugRevit.db"))
{
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "select * from users";
var reader = cmd.ExecuteReader();
while (reader.Read())
{
var id = reader["id"];
var wxid = reader["wxid"];
var city = reader["City"];
}
}
}

  

代码如上,这是原生的写法,这种写法,不需要研究,便可以执行,在网上稍微找一下就好。百度就不用解释了,在IT领域,想解决稍微难一点的问题,基本没有百度什么事。你懂的。
我在研究过程中,几度想要放弃,因为原生写法是可以实现并使用Sqlite的,所以并不是非得找到结合EF6等ORM框架的实现方式不可。我之所以不断研究,是因为我想要使用EF6的ORM框架,而不是原生的方式。

使用EF6,我就可以获得类似下面的写法,就不需要依靠原始的手段转换来转换去:

/// <summary>
    /// 仓储基类
    /// </summary>
    /// <typeparam name="TEntity">Type of the Entity for this repository</typeparam>
    /// <typeparam name="TPrimaryKey">Primary key of the entity</typeparam>
    public abstract class RepositoryBase<TDbContext, TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey>
        where TEntity : class, IEntity<TPrimaryKey>, new()
        where TDbContext : DbContext
    {
        /// <summary>
        /// 数据上下文
        /// </summary>
        protected virtual TDbContext Context { get; }

        /// <summary>
        /// 主键id
        /// </summary>
        public TPrimaryKey Id { get; set; }

        /// <summary>
        /// 获取给定类型的表
        /// </summary>
        public virtual DbSet<TEntity> Table => Context.Set<TEntity>();

        /// <summary>
        /// 附加实体,若不存在的话
        /// </summary>
        /// <param name="entity"></param>
        protected virtual void AttachIfNot(TEntity entity)
        {
            if (!Table.Local.Contains(entity))
            {
                Table.Attach(entity);
            }
        }

        /// <summary>
        /// 判断是否添加或更新
        /// </summary>
        /// <returns></returns>
        protected virtual bool IsTransient()
        {
            bool isTransient;
            if (EqualityComparer<TPrimaryKey>.Default.Equals(Id, default(TPrimaryKey)))
            {
                isTransient = true;
            }
            else
            {
                isTransient = false;
            }

            if (typeof(TPrimaryKey) == typeof(int))
            {
                isTransient = Convert.ToInt32(Id) <= 0;
            }

            if (typeof(TPrimaryKey) == typeof(long))
            {
                isTransient = Convert.ToInt64(Id) <= 0;
            }

            return isTransient;
        }

        /// <summary>
        /// 取全部
        /// </summary>
        /// <returns></returns>
        public virtual IQueryable<TEntity> GetAll()
        {
            Context.Database.Log = Console.Write;
            return Table;
        }

        /// <summary>
        /// 取所有
        /// </summary>
        /// <returns></returns>
        public virtual IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            if (propertySelectors == null || propertySelectors.Count() <= 0)
            {
                return GetAll();
            }

            var query = GetAll();

            foreach (var propertySelector in propertySelectors)
            {
                query = query.Include(propertySelector);
            }

            return query;
        }

        /// <summary>
        /// 添加
        /// </summary>
        /// <remarks>
        /// Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.
        /// 处理办法,没实践过
        /// https://stackoverflow.com/questions/6819813/solution-for-store-update-insert-or-delete-statement-affected-an-unexpected-n
        /// </remarks>
        /// <returns></returns>
        public virtual TEntity Insert(TEntity entity)
        {
            Table.Add(entity);

            return entity;
        }

        /// <summary>
        /// 批量添加
        /// </summary>
        /// <param name="entities"></param>
        /// <returns></returns>
        public List<TEntity> InsertRange(List<TEntity> entities)
        {
            Context.Set<TEntity>().AddRange(entities);
            return entities;
        }

        /// <summary>
        /// 修改
        /// </summary>
        /// <returns></returns>
        public virtual void Update(TEntity entity)
        {
            AttachIfNot(entity);
            Context.Entry(entity).State = EntityState.Modified;
        }

        /// <summary>
        /// 修改
        /// </summary>
        /// <param name="entities"></param>
        public void UpdateRange(params TEntity[] entities)
        {
            foreach (TEntity t in entities)
            {
                AttachIfNot(t);
                Context.Entry(t).State = EntityState.Modified;
            }
        }

        /// <summary>
        /// 添加或修改
        /// </summary>
        public virtual void InsertOrUpdate(TEntity entity)
        {
            this.Id = entity.Id;
            if (this.IsTransient())
            {
                this.Insert(entity);
            }
            else
            {
                this.Update(entity);
            }
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <returns></returns>
        public virtual void Delete(TEntity entity)
        {
            AttachIfNot(entity);
            Context.Set<TEntity>().Remove(entity);
        }
}
}

  

我从错误的调试中,发现这个问题的原因在于IExternalCommand,是Revit直接调用,他不会加载app.config中的配置,所以什么东西都没有。
这就意味着,要在IExternalCommand中,动态加载app.config。
我很高兴去一试,发现根本不行。尝试了各种的动态ConfigurationManager.OpenMappedExeConfiguration()方法,发现他并不是真正的加载到运行时,只是让你可以读取该config文件中的配置信息,仅此而已。

但,我们想要的并非是读取app.config中的配置信息,而是运行时加载它,让他生效。不是吗?

于是,通过一翻查找,没有找到直接答案,但却有一个关键方法让我引起了注意,就是 DbProviderFactories.GetFactory("System.Data.SQLite");但在Revit调用时,始终报错:未加载或注册指定的DbProvider程序。我就日了狗了,卡在这里好久,弄不明白要怎么办。

通过上述的DbProviderFactories,想到要动态加载Sqlite数据库工厂实例,方向是对的。
最终通过下述代码得到了DbProvider的实例:

DbProviderFactory factory = System.Data.SQLite.SQLiteFactory.Instance;

实例获得后,我满满高兴,以为可以开始使用SQlite了,结果,报错,没有EntityFramwork节点的相关注册配置信息。

所以问题转换为怎么动态生成EntityFramework节点信息。EntityFramework节点内容大致如下:
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework">
<parameters>
<parameter value="System.Data.SqlServerCe.4.0" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
<provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
<provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
</providers>
</entityFramework>
我找来找去,都没有找到如何操控这个EntityFramework节点内容的相关代码。因为我看到他是微软内部类internal的。不是public类。所以,理论上,外部根本无法访问此类。
于是我转为寻找如何操控该内部类的方法,终于被我找到一篇文章。
一开始我没有觉得这样子就可以了,我已经被使用Sqlite这个问题折腾了2天。基本不带什么希望。
结果,在我最终将要放弃的时候,按文章的代码一试,靠,竟然...竟然...竟然...成功了!!

            this.SetProviderServices("System.Data.SqlClient", System.Data.Entity.SqlServer.SqlProviderServices.Instance);
            this.SetProviderServices("System.Data.SQLite.EF6", service);
            this.SetProviderServices("System.Data.SQLite", service);

  

通过上述的代码,可以注册EntityFramework节点信息,等同于在运行时加载了app.config中的entityFramework段。

于是,在Revit二开范围内,无法加载app.config的情况下,针对如何使用SQlite结合EF6的ORM框架来使用Sqlite数据库的功能就实现了。

原文地址:https://www.cnblogs.com/mazhiyuan/p/13563048.html