Revit:二开使用Sqlite保存本地数据,并配合EF6等ORM框架
前人种树,后人乘凉。
在Revit中如何使用Sqlite是一项非常艰难的事情。
这也是我写在这里的感言,前后花了满满2个工作日,搜遍全网零碎的代码,最终才拼出解决方案。
你想要直接的解决方案,绝对没有,一定是自己摸索解决的。如果你哪天看到有了,那么你看到的文章极有可能是我写的,或者别人转载了我的文章。
使用原生的System.Data.Common调用SQlite是一件轻松简单的事情:
using (var conn = new SQLiteConnection(@"data source=E:\公司项目\RevitDevelopment\bin\Debug\Revit.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数据库的功能就实现了。

浙公网安备 33010602011771号