2.7 深入理解Entity Framework

性能问题几乎是一切ORM框架的通病,对于EF来说,引起性能低的原因主要在以下几个方面。

  • 复杂的对象管理机制
    为了在.NET中更好地管理模型对象,EF提供了一套内部机制管理和跟踪对象的状态,保持对象的一致性,带来方便的同时,降低了性能。
  • 高度封装的执行机制

    在EF应用中,编写的任何一个查询表达式都需要经过分析,解析成SQL语句,然后调用底层的ADO.NET Providers去执行。直接执行SQL语句相比,性能上有一定的降低。

  • 低效的SQL语句

    EF采用映射机制将对象操作转换为SQL语句,SQL语句一般是基于标准模板生成的,不会进行特殊的优化,这与直接针对业务编写的SQL语句去操作数据相比,效率一般会打折扣,特别是复杂的数据库操作。

Linq查询最终生成的SQL语句是什么样的?我们可以使用ToString()方法直接输出T-SQL代码。如示例10所示。

示例10

using (MySchoolContext db = new MySchoolContext())

{

var result = db.Student.Where(n => n.StudentName.Contains("张"));

Console.WriteLine(result);

}

运行结果如图2-10所示。

图2-10 Linq查询生成的SQL语句

当然,EF本身对性能有一系列的优化措施,会使用这写手段的前提是对EF的执行机制有足够的了解。

2.7.1 EF的状态管理

在程序中实现数据的增、删、改操作,EF会监控这些状态的变化,在执行SaveChange()方法时,会根据对象状态的变化执行相应的操作。如示例11所示。

示例11

using (MySchoolContext db = new MySchoolContext())

{

Grade grade = new Grade() { GradeName = "Y3" };

//输出当前对象状态

Console.WriteLine(db.Entry(grade).State);

db.Grade.Add(grade);

Console.WriteLine(db.Entry(grade).State);

db.SaveChanges();

Console.WriteLine(db.Entry(grade).State);

}

示例11中,通过Entry()方法获取模型状态,该方法是DbContext类的成员方法,定义如下:

public DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity)

返回类型 DbEntityEntry<T> 封装了对象状态相关的信息。常用成员如表3-2所示。

表3-2 DbEntityEntry 的主要成员

方法或属性

说明

CurrentValues

获取由此对象表示的跟踪实体的当前属性值

OriginalValues

获取由此对象表示的跟踪实体的原始属性值

State

获取或设置实体的状态

Reload()

从数据库重新加载对象的值

其中,State属性是一个EntityState枚举类型,其取值如下:

Detached:表示对象存在,但没有被跟踪

Unchanged:表示对象尚未经过修改

Added:表示对象为新对象,并且已添加到对象上下文

Deleted:对象已从对象上下文中删除

Modified:表示对象的一个标量属性已更改

通过设置实体的State属性也可以实现对数据的操作,如:

using (MySchoolContext db = new MySchoolContext())

{

Grade grade = new Grade() { GradeName = "Y3" };

db.Entry(grade).State = EntityState.Added;

db.SaveChanges( );

}

除了需要增删改的对象会受状态管理机制管理外,通过EF查询的数据也默认会进行状态管理。可以通过两种方式指定查询不进行状态管理。

方式一:使用AsNoTracking()方法,如示例12所示。

示例12

using (MySchoolContext db = new MySchoolContext())

{

var result = db.Student.AsNoTracking().FirstOrDefault();

Console.WriteLine(db.Entry(result).State);

}

方式二:设置Configuration.AutoDetectChangesEnabled 属性为false,如示例13。

示例13

using (MySchoolContext db = new MySchoolContext())

{

//禁用自动跟踪变化

db.Configuration.AutoDetectChangesEnabled = false;

for (int i = 0; i < 5000; i++)

{

var stu = new Student() { StudentName = "alex",

GradeId = 1, Age = 20 };

db.Student.Add(stu);

}

db.SaveChanges();

}

在使用EF修改或删除数据时,必须先查询对象,然后再对其进行修改或删除。然而现实开发中很多情况都是通过主键删除一条数据。我们可以通过实体的状态特性来进行优化。

示例14

using (MySchool1Entities entities = new MySchool1Entities())

{

//创建替身对象

var stu = new Student { StudentNo = "10001" };

//给实体附加到上下文中

entities.Student.Attach(stu);

//删除

entities.Student.Remove(stu);

entities.SaveChanges();

}

代码中的Attach()方法可以将EntityState.Unchangee状态的对象附加到上下文中。

2.7.2 延迟加载和贪婪加载

  1. 延迟加载

    又称为懒加载,只有每次调用子实体(外键所在的实体)的时候,才去查询数据库, 主表数据加载的时候,不去查询外键所在的从表。

    实现延迟加载需要满足两个条件:

  • poco类是public且不能为sealed。
  • 导航属性需要标记为Virtual。

也可以关闭延迟加载,方法是:

db.Configuration.LazyLoadingEnabled = false;

关闭延迟加载后,查询主表数据时候,主表的中从表实体为null。

示例15

using (dbContext1 db = new dbContext1())

{

Console.WriteLine("---------------- 01-延迟加载 ---------------");

//EF默认就是延迟加载,默认下面的语句就是true,所以下面语句注释没有任何影响

db.Configuration.LazyLoadingEnabled = true;

var list = db.Student.ToList(); //此处加载的数据,没有对从表进行任何查询操作

foreach (var stu in list)

{

Console.WriteLine("学生编号:{0},学生姓名:{1}", stu.studentId, stu.studentName);

//下面调用导航属性(一对一的关系) 每次调用时,都要去查询数据库

var stuAddress = stu.StudentAddress;

Console.WriteLine("地址编号:{0},地址名称:{1}",

stuAddress.studentAddressId, stu.studentName);

}

}

  1. 贪婪加载

    又名:立即加载、贪婪加载、预加载。查询主表的时候通过Include()方法一次性将数据查询了出来,在调用从表数据的时候,从缓存中读取,无须查询数据库。

    实现方式:

  • 先关闭延迟加载:db.Configuration.LazyLoadingEnabled = false;
  • 查询主表的同时通过Include把从表数据也查询出来。

示例16

using (dbContext1 db = new dbContext1())

{

Console.WriteLine("------------------- 03-立即加载 ------------------");

//1.关闭延迟加载

db.Configuration.LazyLoadingEnabled = false;

//2. 获取主表数据的同时,通过Include将从表中的数据也全部加载出来

var list = db.Student.Include("StudentAddress").ToList();

foreach (var stu in list)

{

Console.WriteLine("学生编号:{0},学生姓名:{1}", stu.studentId,

stu.studentName);

//这里获取从表中的数据,均是从缓存中获取,无需查询数据库

var stuAddress = stu.StudentAddress;

Console.WriteLine("地址编号:{0},地址名称:{1}",

stuAddress.studentAddressId, stu.studentName);

}

}

  1. 显示加载

    关闭了延迟加载,单纯查询了主表数据,这个时候需要重新查询从表数据,就要用到显式加载了。

    使用步骤:

    ①:单个实体用:Reference

    ②:集合用:Collection

    ③:最后需要Load一下

    示例17

using (dbContext1 db = new dbContext1())

{

Console.WriteLine("----------------- 04-显式加载 ------------------");

//1.关闭延迟加载

db.Configuration.LazyLoadingEnabled = false;

//2.此处加载的数据,不含从表中的数据

var list = db.Student.ToList();

foreach (var stu in list)

{

Console.WriteLine("学生编号:{0},学生姓名:{1}", stu.studentId,

stu.studentName);

//3.下面的这句话,可以开启重新查询一次数据库

//3.1 单个属性的情况用Refercence

db.Entry<Student>(stu).Reference(c => c.StudentAddress).Load();

//3.2 集合的情况用Collection

//db.Entry<Student>(stu).Collection(c => c.StudentAddress).Load();

//下面调用导航属性(一对一的关系) 每次调用时,都要去查询数据库

var stuAddress = stu.StudentAddress;

Console.WriteLine("地址编号:{0},地址名称:{1}",

stuAddress.studentAddressId, stu.studentName);

}

}

  1. 小结

    什么时候使用延迟加载,什么时候又使用贪婪加载呢?

    延迟加载只有在需要使用数据时加载,如果不需要使用实体的关联数据,可以使用延迟加载。使用贪婪加载适用于预先了解要使用什么数据的情况,利用这种方式一次性加载数据,可以减少数据库访问次数。

    从实际情况来看,使用默认的延迟加载就可以了,2次或3次的数据库查询是可以接受的。而循环中多次读取数据库,可以考虑使用贪婪加载。

2.7.3 本地缓存

在使用EF时,有时会多次使用一个查询结果,如示例18所示。

示例18

using (MySchoolEntities db = new MySchoolEntities())

{

//查询全部学生

IQueryable<Student> stus = db.Student;

Console.WriteLine("全部学生姓名:");

foreach (var stu in stus)

{

Console.WriteLine("学生姓名:{0}",stu.StudentName);

}

//查询并输出学生人数

Console.WriteLine("学生人数为:{0}",db.Student.Count());

}

示例18中会产生两次查询,但从需求来看,完全没有必要,因为第二次完全可以利用第一次查询的结果。事实上,完全可以使用EF的缓存功能,直接利用缓存的结果,DbSet<T>的Local属性正是用于提供缓存的数据。

示例18中,将"db.Student.Count()"替换为"db.Student.Local.Count()",这样就不会产生新的查询了。

另外,DbSet<T>提供了 Find()方法,用于通过主键查找实体,其查询速度比First()和FirstOrDefault()方法快的多,并且如果相应的实体已经被DbContext缓存,EF会在缓存中直接返回对应的实体,而不会执行数据库访问。

2.7.4 EF中的事务

EF中的事务主要分为三类,分别是SaveChanges、DbContextTransaction 和

TransactionScope。

  1. SaveChanges事务

    在前面内容中,SaveChanges一次性将本地缓存中所有的状态变化一次性提交到数据库,这就是一个事务,要么统一成功,要么统一回滚。

    示例19

using (DbContext db = new CodeFirstModel())

{

//增加

TestInfor t1 = new TestInfor()

{

id = Guid.NewGuid().ToString("N"),

txt1 = "txt1",

txt2 = "txt2"

};

db.Set<TestInfor>().Add(t1);

//删除

TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "1").FirstOrDefault();

if (t2 != null)

{

db.Set<TestInfor>().Remove(t2);

}

//修改

TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "3").FirstOrDefault();

t3.txt2 = "我是李马茹23";

//SaveChanges事务提交

int n = db.SaveChanges();

Console.WriteLine("数据作用条数:" + n);

}

示例19中,如果三个操作中有任意一个出现错误,就会回滚,结果n为0。

  1. DbContextTransaction 事务

    使用场景:EF调用SQL语句的时候使用该事务、 多个SaveChanges的情况。

    示例20

using (DbContext db = new CodeFirstModel())

{

DbContextTransaction trans = null;

try

{

//开启事务

trans = db.Database.BeginTransaction();

//增加

string sql1 = @"insert into TestInfor values(@id,@txt1,@txt2)";

SqlParameter[] pars1 ={

new SqlParameter("@id",Guid.NewGuid().ToString("N")),

new SqlParameter("@txt1","txt11"),

new SqlParameter("@txt2","txt22")

};

db.Database.ExecuteSqlCommand(sql1, pars1);

//删除

string sql2 = @"delete from TestInfor where id=@id";

SqlParameter[] pars2 ={

new SqlParameter("@id","22")

};

db.Database.ExecuteSqlCommand(sql2, pars2);

//修改

string sql3 = @"update TestInfor set txt1=@txt1 where id=@id";

SqlParameter[] pars3 ={

new SqlParameter("@id","3"),

new SqlParameter("@txt1","二狗子")

};

db.Database.ExecuteSqlCommand(sql3, pars3);

//提交事务

trans.Commit();

Console.WriteLine("事务成功了");

}

catch (Exception ex)

{

Console.WriteLine(ex.Message);

trans.Rollback(); //回滚

}

finally

{

//也可以把该事务写到using块中,让其自己托管,就不需要手动释放了

trans.Dispose();

}

}

DbContextTransaction事务也适用于多个SaveChanges的情况。

示例21

using (DbContext db = new CodeFirstModel())

{

//自动脱管,不需要手动释放

using (DbContextTransaction trans = db.Database.BeginTransaction())

{

try

{

TestInfor t1 = new TestInfor()

{

id = Guid.NewGuid().ToString("N"),

txt1 = "111111111",

txt2 = "222222222222"

};

db.Entry(t1).State = EntityState.Added;

db.SaveChanges();

TestInfor t2 = new TestInfor()

{

id = Guid.NewGuid().ToString("N") + "123",

txt1 = "111111111",

txt2 = "222222222222"

};

db.Entry(t2).State = EntityState.Added;

db.SaveChanges();

trans.Commit();

}

catch (Exception)

{

trans.Rollback();

}

}

}

  1. TransactionScope事务

    该种事务适用于多数据库连接的情况,在此不做介绍,请自行查阅相关资料。

2.7.5 从实体框架回归SQL

EF虽然本身有很多优化机制,但和直接使用ADO.NET相比,还是有一定的性能差距,因此EF在DbContext类的Database属性里提供了ExecuteSqlCommand()和SqlQuery()两个方法,用来直接访问数据库。

  1. ExecuteSqlCommand()

    方法的定义如下:

    public int ExecuteSqlCommand(string sql, params object[] parameters)

    用来执行增、删、改操作,返回结果为受影响行数。

  2. SqlQuery()

    方法的定义如下:

    public DbRawSqlQuery<T> SqlQuery<T>(string sql, params object[] parameters);

    用来执行查询操作,返回结果是一个集合。

示例22

using (MySchool1Entities db = new MySchool1Entities())

{

//执行update语句

string sql = "update grade set gradeName=@gradeName where

gradeId=@gradeId";

SqlParameter[] ps =

{

new SqlParameter("@gradeName","第二学年"),

new SqlParameter("@gradeId",3)

};

int result=db.Database.ExecuteSqlCommand(sql, ps);

if (result>0)

{

Console.WriteLine("数据更新完成!");

}

//执行查询语句

sql = "select * from from student where studentNo=@stuNo";

ps = new SqlParameter[] { new SqlParameter("@stuNo", "S1001234") };

var stu = db.Database.SqlQuery<Student>(sql, ps);

Console.WriteLine(stu.ToList()[0]);

}

2.8 封装EF的DAL层

     public class BaseDAL<T> where T:class
{
private DbContext db
{
get
{
DbContext dbContext = CallContext.GetData("dbContext") as DbContext;
if (dbContext == null)
{
dbContext = new MySchoolContext();
CallContext.SetData("dbContext", dbContext);
}
return dbContext;
}
} /// <summary>
/// 执行增加,删除,修改操作(或调用存储过程)
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
/// <returns></returns>
public int ExecuteSql(string sql, params SqlParameter[] pars)
{
return db.Database.ExecuteSqlCommand(sql, pars);
} /// <summary>
/// 执行查询操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="pars"></param>
/// <returns></returns>
public List<T> ExecuteQuery(string sql, params SqlParameter[] pars)
{
return db.Database.SqlQuery<T>(sql, pars).ToList();
} /// <summary>
/// 添加
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public int Add(T model)
{
db.Set<T>().Add(model);
return db.SaveChanges();
} /// <summary>
/// 删除(适用于先查询后删除的单个实体)
/// </summary>
/// <param name="model">需要删除的实体</param>
/// <returns></returns>
public int Del(T model)
{
db.Set<T>().Attach(model);
db.Set<T>().Remove(model);
return db.SaveChanges();
} /// <summary>
/// 根据条件删除(支持批量删除)
/// </summary>
/// <param name="delWhere">传入Lambda表达式(生成表达式目录树)</param>
/// <returns></returns>
public int DelBy(Expression<Func<T, bool>> delWhere)
{
var listDels = db.Set<T>().Where(delWhere);
foreach(var model in listDels)
{
db.Set<T>().Attach(model);
db.Set<T>().Remove(model);
}
return db.SaveChanges();
} /// <summary>
/// 修改
/// </summary>
/// <param name="model">修改后的实体</param>
/// <returns></returns>
public int Modify(T model)
{
db.Entry(model).State = EntityState.Modified;
return db.SaveChanges();
} /// <summary>
/// 批量修改
/// </summary>
/// <param name="model">要修改实体中 修改后的属性 </param>
/// <param name="whereLambda">查询实体的条件</param>
/// <param name="proNames">lambda的形式表示要修改的实体属性名</param>
/// <returns></returns>
public int ModifyBy(T model, Expression<Func<T, bool>> whereLambda, params string[] proNames)
{
List<T> listModifes = db.Set<T>().Where(whereLambda).ToList();
Type t = typeof(T);
List<PropertyInfo> proInfos = t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();
Dictionary<string, PropertyInfo> dicPros = new Dictionary<string, PropertyInfo>();
proInfos.ForEach(p =>
{
if (proNames.Contains(p.Name))
{
dicPros.Add(p.Name, p);
}
});
foreach (string proName in proNames)
{
if (dicPros.ContainsKey(proName))
{
PropertyInfo proInfo = dicPros[proName];
object newValue = proInfo.GetValue(model, null);
foreach (T m in listModifes)
{
proInfo.SetValue(m, newValue, null);
}
}
}
return db.SaveChanges();
} /// <summary>
/// 根据条件查询
/// </summary>
/// <param name="whereLambda">查询条件(lambda表达式的形式生成表达式目录树)</param>
/// <returns></returns>
public IQueryable<T> GetListBy(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where(whereLambda);
}
/// <summary>
/// 根据条件排序和查询
/// </summary>
/// <typeparam name="Tkey">排序字段类型</typeparam>
/// <param name="whereLambda">查询条件</param>
/// <param name="orderLambda">排序条件</param>
/// <param name="isAsc">升序or降序</param>
/// <returns></returns>
public IQueryable<T> GetListBy<Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true)
{
if (isAsc)
{
return db.Set<T>().Where(whereLambda).OrderBy(orderLambda);
}
else
{
return db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda);
}
}
/// <summary>
/// 分页查询
/// </summary>
/// <typeparam name="Tkey">排序字段类型</typeparam>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">页容量</param>
/// <param name="whereLambda">查询条件</param>
/// <param name="orderLambda">排序条件</param>
/// <param name="isAsc">升序or降序</param>
/// <returns></returns>
public IQueryable<T> GetPageList<Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true)
{ IQueryable<T> list = null;
if (isAsc)
{
list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda)
.Skip((pageIndex - ) * pageSize).Take(pageSize);
}
else
{
list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda)
.Skip((pageIndex - ) * pageSize).Take(pageSize);
}
return list;
}
/// <summary>
/// 分页查询输出总行数
/// </summary>
/// <typeparam name="Tkey">排序字段类型</typeparam>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">页容量</param>
/// <param name="whereLambda">查询条件</param>
/// <param name="orderLambda">排序条件</param>
/// <param name="isAsc">升序or降序</param>
/// <returns></returns>
public IQueryable<T> GetPageList<Tkey>(int pageIndex, int pageSize, out int rowCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda, bool isAsc = true)
{
IQueryable<T> list = null;
rowCount = db.Set<T>().Where(whereLambda).Count();
if (isAsc)
{
list = db.Set<T>().Where(whereLambda).OrderBy(orderLambda)
.Skip((pageIndex - ) * pageSize).Take(pageSize);
}
else
{
list = db.Set<T>().Where(whereLambda).OrderByDescending(orderLambda)
.Skip((pageIndex - ) * pageSize).Take(pageSize);
}
return list;
} }

<<BaseDAL.cs>>

APS.NET MVC + EF (02)---深入理解ADO.NET Entity Framework的更多相关文章

  1. APS.NET MVC + EF (02)---ADO.NET Entity FrameWork

    2.1 Entity Framework简介 Ado.net Entity Framework 是Microsoft推出的ORM框架. 2.1.1 什么是ORM 对象关系映射(Object Relat ...

  2. ADO.NET Entity Framework CodeFirst 如何输出日志(EF 5.0)

    ADO.NET Entity Framework CodeFirst 如何输出日志(EF4.3) 用的EFProviderWrappers ,这个组件好久没有更新了,对于SQL执行日志的解决方案的需求 ...

  3. ADO.NET Entity Framework(EF)

    ylbtech-Miscellaneos: ADO.NET Entity Framework(EF) A,返回顶部 1, ADO.NET Entity Framework 是微软以 ADO.NET 为 ...

  4. Entity FrameWork(实体框架)是以ADO.NET Entity FrameWork ,简称为EF

    Entity FrameWork(实体框架)是以ADO.NET Entity FrameWork ,简称为EF Entity FrameWork的特点 1.支持多种数据库(MSSQL.Oracle.M ...

  5. 如何得到EF(ADO.NET Entity Framework)查询生成的SQL? ToTraceString Database.Log

    ADO.NET Entity Framework ToTraceString  //输出单条查询 DbContext.Database.Log  //这里有详细的日志

  6. Microsoft SQL Server Compact 4.0&&ADO.NET Entity Framework 4.1&&MVC3

    最近重新查看微软MvcMusicStore-v3.0的源代码,发现忽略了很多重要的东西,特别是数据访问那一部分. 首先Microsoft SQL Server Compact 4.0 详细的介绍和下载 ...

  7. ADO.NET Entity Framework 在哪些场景下使用?

    在知乎回答了下,顺手转回来. Enity Framework已经是.NET下最主要的ORM了.而ORM从一个Mapping的概念开始,到现在已经得到了一定的升华,特别是EF等对ORM框架面向对象能力的 ...

  8. ADO.NET Entity Framework学习笔记(3)ObjectContext

    ADO.NET Entity Framework学习笔记(3)ObjectContext对象[转]   说明 ObjectContext提供了管理数据的功能 Context操作数据 AddObject ...

  9. 让ADO.NET Entity Framework 支持ACCESS数据库

    如写的不好请见谅,本人水平有限. 个人简历及水平:. http://www.cnblogs.com/hackdragon/p/3662599.html 接到一个程序和网页交互的项目,用ADO.NET ...

随机推荐

  1. The Preliminary Contest for ICPC Asia Shenyang 2019 H. Texas hold'em Poker

    题目链接:https://nanti.jisuanke.com/t/41408 题目意思很简单,就是个模拟过程. #include <iostream> #include <cstr ...

  2. HDU6583:Typewriter(dp+后缀自动机)

    传送门 题意: 给出\(p,q\),现在要你生成一个字符串\(s\). 你可以进行两种操作:一种是花费\(p\)的代价随意在后面添加一个字符,另一种是花费\(q\)的代价可以随意赋值前面的一个子串. ...

  3. Jmter(一)_时间戳

    显示当前时间的使用Jmeter-Tools-Function Helper Dialog的__time 显示当前时间移动的使用__timeShift 有日期移动(e.g. P2D);时(PT2H);分 ...

  4. vs2008 vc90.pdb 不是创建此预编译头时使用的 pdb 文件,请重新创建预编译头

    解决方案: 找到项目中的stdafx.cpp,右键属性,找到C/C++->预编译头, 设置为创建预编译头, 重新生成

  5. Nginx反向代理服务器的安装与配置

    Nginx反向代理服务器的安装与配置 时间:10月19日 Nginx反向代理有不少需要我们解决的问题,其中有不少问题是基于安装上的问题,在安装完成后的相关调试也让很多人头疼不已.下面就向大家介绍有关于 ...

  6. 关于System.FormatException异常

    什么是FormatException 参数格式无效或复合格式字符串不正确时引发的异常. 继承 Object Exception SystemException FormatException 详细说明 ...

  7. Selenium XPath

    目录 1.selenium是什么呢? 安装 设置浏览器引擎 2.基本使用 3.等待元素被加载 4.选择器 2. find_element_by_tag_name 3. find_element_by_ ...

  8. 【LG3322】[SDOI2015]排序

    [LG3322][SDOI2015]排序 题面 洛谷 题解 交换顺序显然不影响答案,所以每种本质不同的方案就给答案贡献次数的阶乘. 从小往大的交换每次至多\(4\)中决策,复杂度\(O(4^n)\). ...

  9. wpf radiobuttong 去前面的圆点, 自定义radiobutton样式

    自定义radiobutton样式代码: <windows.Resources> <LinearGradientBrush x:Key="CheckRadioFillNorm ...

  10. python3 mqtt 添加用户名以及密码

    import paho.mqtt.client as mqtt client = mqtt.Client(client_id, transport='tcp') client.username_pw_ ...