使用实体框架执行SQL查询的匿名类型结果

49

我正在使用实体框架 5.0 和 .NET Framework 4.0 的 Code First 方法。现在我知道可以通过以下方式在实体框架中运行原生SQL:

var students = Context.Database.SqlQuery<Student>("select * from student").ToList();

它的工作完美,但我想要返回匿名结果。例如,我只想要学生表中特定列,如下所示

var students = Context.Database.SqlQuery<Student>("select FirstName from student").ToList();

它不起作用。它产生异常。

数据读取器与指定的“MyApp.DataContext.Student”不兼容。类型的成员“StudentId”在数据读取器中没有相应名称的列。

所以我尝试了dynamic类型。

var students = Context.Database.SqlQuery<dynamic>("select FirstName from student").ToList();

它也没有起作用,返回一个空对象。其中没有可用数据。

有没有办法从动态SQL查询中获取匿名类型的结果?


什么是学生类和学生表? - abatishchev
7个回答

50

你需要使用原始的Sql语句来实现,实体框架 SqlQuery<T> 仅适用于已知类型的对象。

这是我使用的方法:

public static IEnumerable<dynamic> DynamicListFromSql(this DbContext db, string Sql, Dictionary<string, object> Params)
{
    using (var cmd = db.Database.Connection.CreateCommand())
    {
        cmd.CommandText = Sql;
        if (cmd.Connection.State != ConnectionState.Open) { cmd.Connection.Open(); }

        foreach (KeyValuePair<string, object> p in Params)
        {
            DbParameter dbParameter = cmd.CreateParameter();
            dbParameter.ParameterName = p.Key;
            dbParameter.Value = p.Value;
            cmd.Parameters.Add(dbParameter);
        }

        using (var dataReader = cmd.ExecuteReader())
        {
            while (dataReader.Read())
            {
                var row = new ExpandoObject() as IDictionary<string, object>;
                for (var fieldCount = 0; fieldCount < dataReader.FieldCount; fieldCount++)
                {
                    row.Add(dataReader.GetName(fieldCount), dataReader[fieldCount]);
                }
                yield return row;
            }
        }
    }
}

您可以像这样调用它:

List<dynamic> results = DynamicListFromSql(myDb,"select * from table where a=@a and b=@b", new Dictionary<string, object> { { "a", true }, { "b", false } }).ToList();

2
感谢您的回复。除了使用ExpandoObject之外,还可以使用Newtonsoft.Json.Linq.JObject - Ehsan88
3
答案看起来像这样:https://github.com/aspnet/EntityFrameworkCore/issues/2344#issuecomment-172641417 - MattBH
3
如果传递了空值,则可能需要添加以下更新:dbParameter.Value = p.Value == null ? DBNull.Value : p.Value; - LUKE
很棒的解决方案!只有一个小修正 - 由于这是扩展方法,我认为使用示例需要是 "List<dynamic> results = myDb.DynamicListFromSql("select * from table..." - Oggy

31

这是最终解决方案,对我非常有效。

public static System.Collections.IEnumerable DynamicSqlQuery(this Database database, string sql, params object[] parameters)
        {
            TypeBuilder builder = createTypeBuilder(
                    "MyDynamicAssembly", "MyDynamicModule", "MyDynamicType");

            using (System.Data.IDbCommand command = database.Connection.CreateCommand())
            {
                try
                {
                    database.Connection.Open();
                    command.CommandText = sql;
                    command.CommandTimeout = command.Connection.ConnectionTimeout;
                    foreach (var param in parameters)
                    {
                        command.Parameters.Add(param);
                    }

                    using (System.Data.IDataReader reader = command.ExecuteReader())
                    {
                        var schema = reader.GetSchemaTable();

                        foreach (System.Data.DataRow row in schema.Rows)
                        {
                            string name = (string)row["ColumnName"];
                            //var a=row.ItemArray.Select(d=>d.)
                            Type type = (Type)row["DataType"];
                            if(type!=typeof(string) && (bool)row.ItemArray[schema.Columns.IndexOf("AllowDbNull")])
                            {
                                type = typeof(Nullable<>).MakeGenericType(type);
                            }
                            createAutoImplementedProperty(builder, name, type);
                        }
                    }
                }
                finally
                {
                    database.Connection.Close();
                    command.Parameters.Clear();
                }
            }

            Type resultType = builder.CreateType();

            return database.SqlQuery(resultType, sql, parameters);
        }

        private static TypeBuilder createTypeBuilder(
            string assemblyName, string moduleName, string typeName)
        {
            TypeBuilder typeBuilder = AppDomain
                .CurrentDomain
                .DefineDynamicAssembly(new AssemblyName(assemblyName),
                                       AssemblyBuilderAccess.Run)
                .DefineDynamicModule(moduleName)
                .DefineType(typeName, TypeAttributes.Public);
            typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
            return typeBuilder;
        }

        private static void createAutoImplementedProperty(
            TypeBuilder builder, string propertyName, Type propertyType)
        {
            const string PrivateFieldPrefix = "m_";
            const string GetterPrefix = "get_";
            const string SetterPrefix = "set_";

            // Generate the field.
            FieldBuilder fieldBuilder = builder.DefineField(
                string.Concat(PrivateFieldPrefix, propertyName),
                              propertyType, FieldAttributes.Private);

            // Generate the property
            PropertyBuilder propertyBuilder = builder.DefineProperty(
                propertyName, System.Reflection.PropertyAttributes.HasDefault, propertyType, null);

            // Property getter and setter attributes.
            MethodAttributes propertyMethodAttributes =
                MethodAttributes.Public | MethodAttributes.SpecialName |
                MethodAttributes.HideBySig;

            // Define the getter method.
            MethodBuilder getterMethod = builder.DefineMethod(
                string.Concat(GetterPrefix, propertyName),
                propertyMethodAttributes, propertyType, Type.EmptyTypes);

            // Emit the IL code.
            // ldarg.0
            // ldfld,_field
            // ret
            ILGenerator getterILCode = getterMethod.GetILGenerator();
            getterILCode.Emit(OpCodes.Ldarg_0);
            getterILCode.Emit(OpCodes.Ldfld, fieldBuilder);
            getterILCode.Emit(OpCodes.Ret);

            // Define the setter method.
            MethodBuilder setterMethod = builder.DefineMethod(
                string.Concat(SetterPrefix, propertyName),
                propertyMethodAttributes, null, new Type[] { propertyType });

            // Emit the IL code.
            // ldarg.0
            // ldarg.1
            // stfld,_field
            // ret
            ILGenerator setterILCode = setterMethod.GetILGenerator();
            setterILCode.Emit(OpCodes.Ldarg_0);
            setterILCode.Emit(OpCodes.Ldarg_1);
            setterILCode.Emit(OpCodes.Stfld, fieldBuilder);
            setterILCode.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getterMethod);
            propertyBuilder.SetSetMethod(setterMethod);
        }    

如果选择查询仅从单个表和单个实体中检索,则最适合使用该解决方案。我们能否改进此解决方案,以实现对具有JOINS的Select查询进行绑定到不同TypeBuilders的多个表的操作?您有什么想法? - gee'K'iran
这个会在数据库中执行两次。 - ms007
2
你怎么使用这个?我试图在代码中使用它,但是无法成功。你能展示一些调用的样例吗?谢谢! - TheGeekYouNeed
1
嗨@Manish,请分享示例项目。 - Arun D
1
该帖子是关于SqlQuery的问题。但是它没有使用那个函数。 - David Dombrowsky

6

1
谢谢,我正在为代码放置的位置苦苦挣扎,是控制器助手类吗?由于它是公共静态的。 - John
2
看起来还有一种更简单的方法。请前往此处并查看ChristineBoersen的帖子--https://github.com/aspnet/EntityFramework/issues/2344 - goroth
1
@goroth请注意,您正在引用EF Core。 - JP Hellemons

2
如果您有一个实体,只想获取其中一些属性,那么可以借助反射来获得更好的解决方案。
这段代码基于上面答案中的示例。
除此之外,您还可以指定要获取回来的类型和字段数组。
结果的类型为IEnumerable。
public static class DatabaseExtension
{
    public static IEnumerable<T> DynamicSqlQuery<T>(this Database database, string[] fields, string sql, params object[] parameters) where T : new()
    {
        var type = typeof (T);

        var builder = CreateTypeBuilder("MyDynamicAssembly", "MyDynamicModule", "MyDynamicType");

        foreach (var field in fields)
        {
            var prop = type.GetProperty(field);
            var propertyType = prop.PropertyType;
            CreateAutoImplementedProperty(builder, field, propertyType);
        }

        var resultType = builder.CreateType();

        var items = database.SqlQuery(resultType, sql, parameters);
        foreach (object item in items)
        {
            var obj = new T();
            var itemType = item.GetType();
            foreach (var prop in itemType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                var name = prop.Name;
                var value = prop.GetValue(item, null);
                type.GetProperty(name).SetValue(obj, value);
            }
            yield return obj;
        }
    }

    private static TypeBuilder CreateTypeBuilder(string assemblyName, string moduleName, string typeName)
    {
        TypeBuilder typeBuilder = AppDomain
            .CurrentDomain
            .DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run)
            .DefineDynamicModule(moduleName)
            .DefineType(typeName, TypeAttributes.Public);
        typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
        return typeBuilder;
    }

    private static void CreateAutoImplementedProperty(TypeBuilder builder, string propertyName, Type propertyType)
    {
        const string privateFieldPrefix = "m_";
        const string getterPrefix = "get_";
        const string setterPrefix = "set_";

        // Generate the field.
        FieldBuilder fieldBuilder = builder.DefineField(
            string.Concat(privateFieldPrefix, propertyName),
                          propertyType, FieldAttributes.Private);

        // Generate the property
        PropertyBuilder propertyBuilder = builder.DefineProperty(
            propertyName, PropertyAttributes.HasDefault, propertyType, null);

        // Property getter and setter attributes.
        MethodAttributes propertyMethodAttributes =
            MethodAttributes.Public | MethodAttributes.SpecialName |
            MethodAttributes.HideBySig;

        // Define the getter method.
        MethodBuilder getterMethod = builder.DefineMethod(
            string.Concat(getterPrefix, propertyName),
            propertyMethodAttributes, propertyType, Type.EmptyTypes);

        // Emit the IL code.
        // ldarg.0
        // ldfld,_field
        // ret
        ILGenerator getterILCode = getterMethod.GetILGenerator();
        getterILCode.Emit(OpCodes.Ldarg_0);
        getterILCode.Emit(OpCodes.Ldfld, fieldBuilder);
        getterILCode.Emit(OpCodes.Ret);

        // Define the setter method.
        MethodBuilder setterMethod = builder.DefineMethod(
            string.Concat(setterPrefix, propertyName),
            propertyMethodAttributes, null, new Type[] { propertyType });

        // Emit the IL code.
        // ldarg.0
        // ldarg.1
        // stfld,_field
        // ret
        ILGenerator setterILCode = setterMethod.GetILGenerator();
        setterILCode.Emit(OpCodes.Ldarg_0);
        setterILCode.Emit(OpCodes.Ldarg_1);
        setterILCode.Emit(OpCodes.Stfld, fieldBuilder);
        setterILCode.Emit(OpCodes.Ret);

        propertyBuilder.SetGetMethod(getterMethod);
        propertyBuilder.SetSetMethod(setterMethod);
    }    
}

您可以这样调用:
var fields = new[]{ "Id", "FirstName", "LastName" };
var sql = string.Format("SELECT {0} FROM People WHERE Id = @id", string.Join(", ", fields));

var person = db.Database.DynamicSqlQuery<People>(fields, sql, new SqlParameter("id", id))
    .FirstOrDefault();

实际上,它仅适用于简单类型,并且没有错误处理。


typeof(T) 返回 null,我该怎么办? - asfandahmed1
在以下代码中,将动态类型更改为对象类型:foreach (object item in items) - ms007

1

我像这样使用它

ORMClass:

public class ORMBase<T, TContext> : IORM<T>
        where T : class
        where TContext : DbContext, IDisposable, new()

方法:

public IList<TResult> GetSqlQuery<TResult>(string sql, params object[] sqlParams)
{
                using (TContext con = new TContext())
                {
                    return Enumerable.ToList(con.Database.SqlQuery<TResult>(sql, sqlParams));
                }
}

最后使用:
public class ResimORM : ORMBase<Resim, mdlOgrenciKulup>, IDisposable
{
    public void Dispose() { GC.SuppressFinalize(this); }
}





ResimORM RE_ORM = new ResimORM();
List<Resim> a = RE_ORM.GetSqlQuery<Resim>(sql,null).ToList();
int b = RE_ORM.GetSqlQuery<int>(sql,null).FirstOrDefault();

-1

我知道现在可能有点晚了,但如果你想执行标量函数并始终将其转换为统一类型,可以使用这种方法。

只需使用forceStringCast参数来强制将SQL标量函数结果转换为字符串,然后按照您的需要使用此字符串即可。

public static Task<T> ScalarFunction<T>(DbContext db, string sql, bool forceStringCast = false, Dictionary<string, object> parameters = null) 
    {
        string cmdText;

        if (forceStringCast)
        {
            cmdText = $@"SELECT CAST({sql}({string.Join(",", 
                parameters.Keys.Select(p => "@" + p).ToList())} AS VARCHAR(MAX)));";
        }
        else
        {
            cmdText =
                $@"SELECT {sql}({string.Join(",", 
                    parameters.Keys.Select(p => "@" + p).ToList())});";
        }

        return db.Database.SqlQuery<T>(cmdText,parameters.Select(p => new SqlParameter(p.Key, p.Value)).ToArray()).FirstOrDefaultAsync();
    }

-2

这是我的解决方案:

DbContext.Database.Connection.Open();

            var cmd = DbContext.Database.Connection.CreateCommand();
            cmd.CommandText = @"Select * from Table where Param1 = @Param1 and Param2 = @Param2";
            cmd.Parameters.Add(new SqlParameter("Param1", Mahdi));
            cmd.Parameters.Add(new SqlParameter("Param2", 20));

            List<Dictionary<string, object>> items = new List<Dictionary<string, object>>();
            var reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                Dictionary<string, object> item = new Dictionary<string, object>();

                for (int i = 0; i < reader.FieldCount; i++)
                    item[reader.GetName(i)] = (reader[i]);
                items.Add(item);
            }

            return Request.CreateResponse(HttpStatusCode.OK, items);

希望能对大家有所帮助 ;)


这并没有回答问题,而且已经给出了唯一正确的答案。(请不要在多个问题中发布相同的答案)。 - Gert Arnold
亲爱的Gert Arnold, 这是被问问题的正确、简短和最佳解决方案。 再次检查一下 ;) - Mahdi
问题是关于 SqlQuery,然而这个答案已经给出了。 - Gert Arnold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接