Visual Studio Visualizer:第二部分——Entity Framework





5.00/5 (15投票s)
此项目为 Entity Framework 查询创建一个 Visual Studio 可视化工具,用于查看、编辑和运行生成的 SQL。
引言
此项目为 Entity Framework 查询创建一个 Visual Studio 可视化工具,用于查看、编辑和运行生成的 SQL。 还可以选择查看 String
、StringBuilder
、XDocument
、SqlCommand
和 OleDbCommand
对象。
项目网站:http://vsdatawatchers.codeplex.com。
背景
第 1 部分可在 此处 找到。
如果您不熟悉 Visual Studio 可视化工具,建议阅读第 1 部分。
第 3 部分可在 此处 找到。
实体框架
此可视化工具的主要重点是 Entity Framework 生成的 SQL。 Entity Framework 对象有两个不同的基本上下文,即 ObjectContext
和 DbContext
。
ObjectContext
由默认的代码生成使用,而 DbContext
由 Entity Framework 5 代码生成器的默认模板使用。
在每个上下文中,都有两种不同的操作类型,即对模型的查询,例如
DateTime data = new DateTime(1980, 1, 1);
var t = from e in entities.Employee
where e.BirthDate > data
select e;
其中可视化对象是 t
。
其他操作是其他的 CRUD,即 update
、delete
和 create
,例如
List<EFObjectsOC.Employee> employees = t.ToList();
employees.FirstOrDefault().JobTitle = "Up up";
EFObjectsOC.Employee employeeToDelete = employees.LastOrDefault();
entities.Employee.DeleteObject(employeeToDelete);
EFObjectsOC.Employee newEmployee = new EFObjectsOC.Employee();
newEmployee.HireDate = DateTime.Now;
newEmployee.Gender = "M";
newEmployee.JobTitle = "new job";
newEmployee.NationalIDNumber = "987654321";
newEmployee.BusinessEntityID = 987654321;
EFObjectsOC.Employee newEmployee2 = new EFObjectsOC.Employee();
newEmployee2.HireDate = DateTime.Now;
newEmployee2.Gender = "M";
newEmployee2.JobTitle = "new job";
newEmployee2.NationalIDNumber = "123456789";
newEmployee2.BusinessEntityID = 123456789;
entities.AddToEmployee(newEmployee);
entities.AddToEmployee(newEmployee2);
其中可视化对象是 entities
。
Select 语句
将鼠标悬停在 t
上,然后选择可视化工具,我们会得到一个 SQL 编辑器,其中包含 select
语句以及针对数据库运行查询的可能性
对于 SQL 语言,编辑器启用了更多选项
我们甚至可以检查连接字符串,运行查询并查看返回的数据
Insert/Update/Delete 语句
要查看 insert
/update
/delete
语句的 SQL,请将鼠标悬停在 entities
上,然后选择可视化工具
还有一个针对添加到上下文的实体的错误检查
代码
此可视化工具已注册用于 ObjectQuery
、ObjectContext
、DbQuery<>
和 DbContext
。 ObjectQuery
和 DbQuery
分别用于对 ObjectContext
和 DbContext
上下文对象的查询语句,ObjectContext
和 DbContext
注册用于对上下文进行的所有其他 CRUD 操作。
如果您不熟悉 Visual Studio 可视化工具,请阅读 此。
ObjectQuery
SQLQueryOptions
只是将由可视化工具呈现的属性的包装器。 首先,我们将使用 ToTraceString()
方法获取生成的 SQL,然后获取使用的所有参数,结果将是一个有效的 SQL 查询
public static SQLQueryOptions ToSqlString(this ObjectQuery objectQuery)
{
SQLQueryOptions sqlOptions = new SQLQueryOptions();
string sql = objectQuery.ToTraceString();
StringBuilder sb = new StringBuilder();
if (objectQuery.Context != null && objectQuery.Context.Connection != null
&& objectQuery.Context.Connection is EntityConnection)
sqlOptions.ConnectionString =
(objectQuery.Context.Connection as EntityConnection).StoreConnection.ConnectionString;
foreach (ObjectParameter parameter in objectQuery.Parameters)
{
SqlParameter r = new SqlParameter(parameter.ParameterType.FullName, parameter.Value);
sb.AppendLine(string.Format("DECLARE @{0} {1}{2};",
parameter.Name, r.SqlDbType, ContextHelper.GetTypeLength(r.SqlDbType)));
sb.AppendLine(string.Format("SET @{0} = '{1}';", parameter.Name, parameter.Value));
}
sb.AppendLine();
sb.AppendLine(sql);
sqlOptions.SQLCommand = sb.ToString();
return sqlOptions;
}
DbQuery<>
对于 DbQuery<>
,我们必须使用反射获取值,此代码在 stackoverflow 中找到
public static SQLQueryOptions ToSqlString(this IQueryable queryable)
{
SQLQueryOptions sqlOptions = new SQLQueryOptions();
var iqProp = queryable.GetType().GetProperty("InternalQuery",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var iq = iqProp.GetValue(queryable);
var oqProp = iq.GetType().GetProperty("ObjectQuery",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var oq = oqProp.GetValue(iq);
var objectQuery = oq as ObjectQuery;
return objectQuery.ToSqlString();
}
在这里,我们可以使用为 ObjectQuery
开发的扩展方法 ToSqlString()
。
ObjectContext
ObjectContext
也使用反射来获取生成的 SQL
public static SQLQueryOptions ToSqlString(this ObjectContext objectContext)
{
SQLQueryOptions sqlOptions = new SQLQueryOptions();
sqlOptions.ConnectionString =
(objectContext.Connection as EntityConnection).StoreConnection.ConnectionString;
sqlOptions.SQLCommand = ContextHelper.GetSQLCommands(objectContext);
return sqlOptions;
}
辅助方法 GetSQLCommands
internal static string GetSQLCommands(ObjectContext context)
{
StringBuilder sql = new StringBuilder();
foreach (DbCommand command in ContextHelper.GetContextCommands(context))
{
sql.Append("------------------------------------");
sql.AppendLine();
sql.Append("-- Command");
sql.AppendLine();
sql.Append("------------------------------------");
sql.AppendLine();
foreach (DbParameter parameter in command.Parameters)
{
System.Data.SqlClient.SqlParameter r = new System.Data.SqlClient.SqlParameter(
parameter.DbType.ToString(), parameter.Value);
sql.AppendLine(string.Format("DECLARE {0} {1}{2};",
parameter.ParameterName,
r.SqlDbType, ContextHelper.GetTypeLength(r.SqlDbType)));
sql.AppendLine(string.Format("SET {0} = '{1}';",
parameter.ParameterName, parameter.Value));
}
sql.AppendLine();
sql.Append(command.CommandText);
sql.AppendFormat("{0}GO{0}{0}", Environment.NewLine);
}
return sql.ToString();
}
实际的反射在此部分中,此代码在 Entity Framework 论坛中找到
internal static IEnumerable<DbCommand> GetContextCommands(ObjectContext context)
{
const string EntityAssemblyName =
"System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
var entityAssemly = Assembly.Load(EntityAssemblyName);
var updateTranslatorType =
entityAssemly.GetType("System.Data.Mapping.Update.Internal.UpdateTranslator");
var functionUpdateCommandType =
entityAssemly.GetType("System.Data.Mapping.Update.Internal.FunctionUpdateCommand");
var dynamicUpdateCommandType =
entityAssemly.GetType("System.Data.Mapping.Update.Internal.DynamicUpdateCommand");
var ctorParams = new object[]
{
context.ObjectStateManager,
((EntityConnection)context.Connection).GetMetadataWorkspace(),
(EntityConnection)context.Connection,
context.CommandTimeout
};
var updateTranslator = Activator.CreateInstance(
updateTranslatorType,
BindingFlags.NonPublic | BindingFlags.Instance,
null,
ctorParams,
null);
MethodInfo produceCommandsMethod = updateTranslatorType
.GetMethod("ProduceCommands", BindingFlags.Instance | BindingFlags.NonPublic);
var updateCommands = produceCommandsMethod.Invoke(updateTranslator, null) as IEnumerable;
foreach (object o in updateCommands)
{
if (functionUpdateCommandType.IsInstanceOfType(o))
{
FieldInfo mdbCommandField = functionUpdateCommandType.GetField(
"m_dbCommand", BindingFlags.Instance | BindingFlags.NonPublic);
yield return mdbCommandField.GetValue(o) as DbCommand;
}
else if (dynamicUpdateCommandType.IsInstanceOfType(o))
{
MethodInfo createCommandMethod = dynamicUpdateCommandType.GetMethod(
"CreateCommand", BindingFlags.Instance | BindingFlags.NonPublic);
var methodParams = new object[]
{
updateTranslator,
new Dictionary<int, object>()
};
yield return createCommandMethod.Invoke(o, methodParams) as DbCommand;
}
}
}
DbContext
public static SQLQueryOptions ToSqlString(this DbContext dbContext)
{
SQLQueryOptions sqlOptions = new SQLQueryOptions();
var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
sqlOptions.ConnectionString =
(objectContext.Connection as EntityConnection).StoreConnection.ConnectionString;
sqlOptions.SQLCommand = ContextHelper.GetSQLCommands(objectContext);
return sqlOptions;
}
ContextHelper.GetTypeLength()
方法用于为 SQL 参数设置默认长度,例如,如果参数的类型为 nvarchar
,那么我们得到 nvarchar(max)
。 如果没有此长度限定符,则设置为该参数的值将被截断。 因此代码
public static string GetTypeLength(SqlDbType type)
{
switch (type)
{
case SqlDbType.Decimal:
return "(38,38)";
case SqlDbType.Binary:
case SqlDbType.Char:
case SqlDbType.NChar:
return "(8000)";
case SqlDbType.NVarChar:
case SqlDbType.VarBinary:
case SqlDbType.VarChar:
return "(MAX)";
default:
return "";
}
}
String、StringBuilder 和 XDocument
此可视化工具还支持 String
、StringBuilder
和 XDocument
对象。 XDocument
可视化工具添加了一个简单的 XML 高亮显示器
String
和 StringBuilder
类似,如果 string
对象是 SQL 语句,用户可以在“语言”菜单中将语言更改为 SQL,然后会显示一个基本的查询编辑器
历史
- 2013-05-02:文章上传
- 2013-05-07:链接更新
- 2013-06-10:链接更新。引用可视化工具支持的新对象。
- 2013-06-24:SQL 参数生成方面的错误更正。