随笔- 27  评论- 912  文章- 0 

MVC3+EF4.1学习系列(十一)----EF4.1常见的问题解决

文章索引和简介

博客写了10篇了~有很多朋友私信问了一些问题,而且很多问题 大家问的都一样 这里说说这些常见问题的解决办法.如果大家有更好的解决办法~也希望分享出来

问题大概为这几个

一.ef4.1 codeFirst 修改表结构 增加字段等 EF code first需要重新生成库导致数据丢失的问题.

二.ef4.1 没有了edmx等复杂的东西 变得简单 干净  但如何使用存储过程,存储过程可以返回表 可以返回数值 也有可能是执行修改 删除 增加等  该怎么做?

三.ef4.1 如何使用数据库视图?每个视图都要去建立对应的实体类么?有简单的方法么?

四.ef4.1 如何执行SQL函数等操作?

五.ef4.1 如何跨数据库访问?

六.ef4.1执行连接查询?什么时候执行左连接? 什么时候执行内连接? ef 根据什么去判断?

七.新手使用ef4.1 常见的一些报错信息

八.ef4.1返回datatable 方便做报表等一些复杂操作

 

其实这些问题是比较简单的~ 所以此文送给刚用ef4.1的新手~

下面开始一一解决这些问题

一.ef4.1 codeFirst 修改表结构 增加字段等 EF code first需要重新生成库导致数据丢失的问题

说这个问题前 首先先说下 我使用ef4.1 codefirst的目的. 是因为可以有更纯净的POCO 不再有EDMX这些东西  而不是真正的用 code first 先有代码 再生成数据库.所以 我虽然使用

的是codefirst 但是本质依然是数据库优先.

所以这个被问的很多的问题 解决办法其实是非常简单的.只要你的数据库已经存在了 那么即使你用code first ef 也不会给你去生成数据库的. 这个时候 你增加表字段 甚至增加表 只要把

实体类也相应的修改 则数据库里的数据 是不会被清空的.

说下我的开发步骤  先设计数据库 并建立数据库=>通过EF工具生成映射和实体类=>开发代码     当遇到修改时=>  先修改数据库如添加字段或表等=>再修改实体类=>继续开发

这样就不会有重新生成数据的烦恼了 而且项目里也不会出现edmx~


还有 使用EF4.3 的数据迁移功能 也可以完美解决~

二.ef4.1 没有了edmx等复杂的东西 变得简单 干净  但如何使用存储过程,存储过程可以返回表 可以返回数值 也有可能是执行修改 删除 增加等  该怎么做?

说这个问题前 依然先说下我的观点.个人认为 既然使用orm框架  就应该把业务逻辑等 都放到业务逻辑层 而不应该再使用存储过程。我更偏重重业务逻辑层 轻存储过程这样的开发~

再ef4.0里 添加存储过程 比较容易 有edmx 调一调 存储过程就添加上了 但是在ef4.1里 只有干净的poco 不再有edmx了 改怎么办呢?尤其是存储过程可以是查表 查值 或者执行修改删除.

一个一个来解决

1.执行返回表类型的存储过程

先上存储过程 随手写的一个最简单的

Create PROCEDURE [dbo].[ProSelectStu]
@StudentID int
AS
BEGIN

Select Student.* from Enrollment,Student
where Enrollment.StudentID=Student.StudentID
and Enrollment.StudentID=@StudentID

END

GO

执行存储过程的方法 是用直接执行sql的方式 我在我的文章第九篇 有过详细的介绍~大家可以先去看下

执行表的存储过程 其实是非常强大的 延迟加载 等都有体现 博客园的陆老师已经写了 写的非常清楚了~我这里就不再写了 大家可以去他那看下 提供个连接~

EF使用存储过程查询表的

2.执行返回值的存储过程

先上存储过程

CREATE PROCEDURE [dbo].[ProSelectCount]
@StuId int
AS
BEGIN
select COUNT(*) from Enrollment where StudentID=@StuId
END

一个简单的查询数量

这里用sqlQuery 执行访问 数据库 因为需要提供返回类型 而我们返回的是int 所以先得到int的类型

3.执行增删改

CREATE PROCEDURE [dbo].[ProDel]
@stuId int,
@courseId int
AS
BEGIN

DELETE FROM [WLFSchool].[dbo].[Enrollment]
where StudentID=@stuId and CourseID=@courseId

END

这个用的是操作数据库 返回受影响行数


三.ef4.1 如何使用数据库视图?每个视图都要去建立对应的实体类么?有简单的方法么?

先说下最传统的方法 只需把视图 当成表 建立对应的实体类  然后加到dbcontext 里即可。没什么难度。

再说一个问题 使用linq 有个非常美妙的功能 投影映射 和C#3.0的 匿名函数 让我们很多情况 不需要视图的

                                  from c in classes
from s in students
where c.ClassID == s.ClassID
order by c.CreateTime
select new
{
Name = s.Name,
Age = s.Age,
ClassName = c.ClassName
};

再通过  var result 接受上面的值  这样我们就不用去数据库建视图 不用再建实体类 是不是很省事呢?

如果公司强大的DBA 已经给我们建好了很多视图 是不是就要一个个去写实体类呢?如果你使用的是C#4.0 那么可以用动态的 来解决这个问题~

像下面这样使用 是不是很爽

这个不仅可以查询视图 普通的表 只要是SQL语句 都可以自动生成动态类 让你用~

下面是扩展方法  和 使用Emit 来动态构建 感谢 ASP.NET 韋 给的帮助~~

SqlQueryForDynamic的扩展方法
  public static class DatabaseExtensions
{
public static IEnumerable SqlQueryForDynamic(this Database db,
string sql,
params object[] parameters)
{
IDbConnection defaultConn = new System.Data.SqlClient.SqlConnection();

return SqlQueryForDynamicOtherDB(db, sql, defaultConn, parameters);
}

public static IEnumerable SqlQueryForDynamicOtherDB(this Database db,
string sql,
IDbConnection conn,
params object[] parameters)
{
conn.ConnectionString = db.Connection.ConnectionString;

if (conn.State != ConnectionState.Open)
{
conn.Open();
}

IDbCommand cmd = conn.CreateCommand();
cmd.CommandText = sql;

IDataReader dataReader = cmd.ExecuteReader();

if (!dataReader.Read())
{
return null; //无结果返回Null
}

#region 构建动态字段

TypeBuilder builder = DatabaseExtensions.CreateTypeBuilder(
"EF_DynamicModelAssembly",
"DynamicModule",
"DynamicType");

int fieldCount = dataReader.FieldCount;
for (int i = 0; i < fieldCount; i++)
{
//dic.Add(i, dataReader.GetName(i));

//Type type = dataReader.GetFieldType(i);

DatabaseExtensions.CreateAutoImplementedProperty(
builder,
dataReader.GetName(i),
dataReader.GetFieldType(i));
}

#endregion

dataReader.Close();
dataReader.Dispose();
cmd.Dispose();
conn.Close();
conn.Dispose();

Type returnType = builder.CreateType();

if (parameters != null)
{
return db.SqlQuery(returnType, sql, parameters);
}
else
{
return db.SqlQuery(returnType, sql);
}
}

public 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;
}

public 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);
}

}


四.ef4.1 如何执行SQL函数等操作?

添加引用  System.Data.Objects.SqlClient.SqlFunctions 主要是这个命名空间

使用方法~上一个工作中的例子~

var query = from s in student.T_StudentInfo
where SqlFunctions.DateDiff("day", s.CreateTime, "2011/11/4") == 0
select s.StudentName;

使用SQL 的datadiff 函数~~

五.ef4.1 如何跨数据库访问?

每次别人问我这个问题 毫不犹豫的把站长dudu的文章发过去~ 他已经很好的解决了~

http://www.cnblogs.com/dudu/archive/2011/03/29/entity_framework_cross_database_query_fact.html

核心思路 欺骗SQL 利用创建同义词去实现

六.ef4.1执行连接查询?什么时候执行左连接? 什么时候执行内连接? ef 根据什么去判断?

当我们做多表查询时  用Include 强制加载 或用 select 去查询时  发现生成的SQL语句 有时是左连接  有时是inner join。

其实EF是根据我们实体类的连接字段 是否可空来判断的~比如外键 studentID

  public  Nullable<int> StudentID { get; set; }

是否可空 就会造成 是 left join 还是 inner join~~

补充下~~ 有个朋友说 这个设为空了 依然执行的是内连接啊~

注意看下你的关系那块  也要设为可空 用这个   HasOptional 而不要用  HasRequired ~~

当你的外键可以为空时 用 HasOptional  否则用 HasRequired

这块也会决定你是内链接 还是 左连接~~

 


七.新手使用ef4.1 常见的一些报错信息

1.执行命令定义时出错

出现这个错的原因有很多  数据库语句错误 我们可以先通过监测SQL 语句是否发送到数据库 然后执行这条SQL语句 看看是否有问题

 造成这个错的原因 还有可能是 连接对象一直被占用 因为EF有延迟加载 只是select时 并没有真正去数据库执行

我们可以先把前面的查询语句 tolist等  再去执行下面的操作

2.

System.Data.Edm.EdmEntityType: : EntityType“Enrollment”未定义键。请为该 EntityType 定义键。
System.Data.Edm.EdmEntitySet: EntityType: EntitySet �Enrollments� 基于未定义键的类型 �Enrollment�。

遇到这种情况 尝试给主键加上[Key]

3.更新条目错误

依然检测数据库语句 是否有外键约束导致插入错误等

4.LINQ to Entities 不识别方法“System.String ToString(System.String)”因此该方法无法转换为存储表达式

  或者不识别其他方法......类似于这样的错误

因为SQL里没有这样的方法 所以无法转换成SQL语句  SqlClient 和Linq Provider没有实现那个方法对应的SQL,所以会提示不支持

解决办法:

1. 把要转换的值提前转换好 而不要再 linq 或拉姆达表示里写 这样的转换语。

    就是把变量 .ToString()  提到外面声明个变量  然后在拉姆达表达式里 直接使用这个变量

2. 转换成 Enumerable

IEnumerable是直接执行方法 ,而不调用Provider来转成其它的方式

 这样会把数据库里的查询出来 然后在内存里操作  所以数据库量大时 效率会低~

 

八.ef4.1使用datatable

datatable 在有的时候是非常有用的 例如 做报表等  因为我们不可能为每个报表建一个 实体类 这样比较麻烦

这个时候返回datatable  则比较有用

写一个扩展方法

   /// <summary>
       /// EF SQL 语句返回 dataTable
       /// </summary>
       /// <param name="db"></param>
       /// <param name="sql"></param>
       /// <param name="parameters"></param>
       /// <returns></returns>
       public static DataTable SqlQueryForDataTatable(this Database db,
                string sql,
                SqlParameter[] parameters)
       {

           SqlConnection conn = new System.Data.SqlClient.SqlConnection();
           conn.ConnectionString = db.Connection.ConnectionString;
           if (conn.State != ConnectionState.Open)
           {
               conn.Open();
           }
           SqlCommand cmd = new SqlCommand();
           cmd.Connection = conn;
           cmd.CommandText = sql;

           if (parameters.Length>0)
           {
               foreach (var item in parameters)
               {
                   cmd.Parameters.Add(item);
               }
           }

          
           SqlDataAdapter adapter = new SqlDataAdapter(cmd);
           DataTable table = new DataTable();
           adapter.Fill(table);
           return table;
       }

调用如下

   protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            GridView1.DataSource = GetDataTable();
            GridView1.DataBind();
        }
    }


    public DataTable GetDataTable()
    {
        GardenHotelContext context = new GardenHotelContext();
        int LanType = 0;
        int state = 0;
        SqlParameter[] sqlparams=new SqlParameter[2];
        sqlparams[0]=new SqlParameter("LanType",LanType);
        sqlparams[1]=new SqlParameter("state",state);
        DataTable DataTable = context.Database.SqlQueryForDataTatable("select LeaveName,LeaveEmail from LeaveInfo where LanType=@LanType and State=@State", sqlparams);
        return DataTable;
     
    }

 

 

再分享一种方法  先上调用效果  利用返回的var 匿名类型  这样就无需声明实体类了

  public DataTable GetDataTable2()
    {
        GardenHotelContext context = new GardenHotelContext();

        var list = (from l in context.LeaveInfoes
                   group l by l.LanType into g
                   select new
                   {
                       g.Key,
                       num = g.Count()
                   }).ToList();

        return PubClass.ListToDataTable(list);

    }

核心方法 反射调用 

      #region  反射List To DataTable


        /// <summary>  
        /// 将集合类转换成DataTable  
        /// </summary>  
        /// <param name="list">集合</param>  
        /// <returns></returns>  
        public static DataTable ListToDataTable(IList list)
        {
            DataTable result = new DataTable();
            if (list.Count > 0)
            {
                PropertyInfo[] propertys = list[0].GetType().GetProperties();
                foreach (PropertyInfo pi in propertys)
                {
                    result.Columns.Add(pi.Name, pi.PropertyType);
                }

                for (int i = 0; i < list.Count; i++)
                {
                    ArrayList tempList = new ArrayList();
                    foreach (PropertyInfo pi in propertys)
                    {
                        object obj = pi.GetValue(list[i], null);
                        tempList.Add(obj);
                    }
                    object[] array = tempList.ToArray();
                    result.LoadDataRow(array, true);
                }
            }
            return result;
        }  

        #endregion

 

 

当然 解决这个问题 也可以用 上面的 动态视图的方法去解决~

九.总结

目前上面这几个问题 被问的比较多~ 写个文章 以后就不用再回答类似的问题啦~

 我的解决方法不是最好的  有更好的解决办法 欢迎回复~期待你的精彩回复!

如果大家还遇到什么EF4.1的问题 或者 mvc3上的问题 都欢迎留言 ~我尽力帮大家

解决~

posted on 2011-12-30 08:40  wlf  阅读(43503)  评论(74编辑  收藏