65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2013年5月1日

CPOL

3分钟阅读

viewsIcon

59382

此项目为 Entity Framework 查询创建一个 Visual Studio 可视化工具,用于查看、编辑和运行生成的 SQL。

引言

此项目为 Entity Framework 查询创建一个 Visual Studio 可视化工具,用于查看、编辑和运行生成的 SQL。 还可以选择查看 StringStringBuilderXDocumentSqlCommandOleDbCommand 对象。

项目网站:http://vsdatawatchers.codeplex.com

背景

第 1 部分可在 此处 找到。

如果您不熟悉 Visual Studio 可视化工具,建议阅读第 1 部分。

第 3 部分可在 此处 找到。

实体框架

此可视化工具的主要重点是 Entity Framework 生成的 SQL。 Entity Framework 对象有两个不同的基本上下文,即 ObjectContextDbContext

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,即 updatedeletecreate,例如

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 上,然后选择可视化工具

还有一个针对添加到上下文的实体的错误检查

代码

此可视化工具已注册用于 ObjectQueryObjectContextDbQuery<>DbContextObjectQueryDbQuery 分别用于对 ObjectContextDbContext 上下文对象的查询语句,ObjectContextDbContext 注册用于对上下文进行的所有其他 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

此可视化工具还支持 StringStringBuilderXDocument 对象。 XDocument 可视化工具添加了一个简单的 XML 高亮显示器

StringStringBuilder 类似,如果 string 对象是 SQL 语句,用户可以在“语言”菜单中将语言更改为 SQL,然后会显示一个基本的查询编辑器

历史

  • 2013-05-02:文章上传
  • 2013-05-07:链接更新
  • 2013-06-10:链接更新。引用可视化工具支持的新对象。
  • 2013-06-24:SQL 参数生成方面的错误更正。
© . All rights reserved.