Emit学习(4) - Dapper解析之数据对象映射(一)
感觉好久没有写博客了, 这几天有点小忙, 接下来会更忙, 索性就先写一篇吧. 后面估计会有更长的一段时间不会更新博客了.
废话不多说, 先上菜.
一、示例
1. 先建类, 类的名称与读取的表名并没有什么关系,可以不一样, 然后就是其中的属性大小写不限
public class Tch_Teacher
{
public int Id { get; set; } public string Name { get; set; } public bool IsDoublePosition { get; set; } public DateTime CreateDate { get; set; }
} public class Test : Tch_Teacher //, ISupportInitialize 此接口有两个方法, BeginInit, EndInit
{
//[ExplicitConstructor] Dapper会优先查找设置了此属性的构造函数
//public Test() { } public string BId { get; set; } //public void BeginInit()
//{
// Console.WriteLine("Test BeginInit");
//} //public void EndInit()
//{
// Console.WriteLine("Test EndInit");
//}
}
2. 测试代码
class Program
{
static void Main(string[] args)
{
var conStr = ConfigurationManager.ConnectionStrings["ConStr"].ToString();
using (IDbConnection conn = new MySqlConnection(conStr))
{
var sql = "select Count(1) from tch_teacher where id>@Id limit 3;";
//Console.WriteLine(conn.Query<int?>(sql, new { Id = 10 })); //error
Console.WriteLine(conn.Query<int>(sql, new { Id = }).FirstOrDefault()); //
Console.WriteLine(conn.Query<string>(sql, new { Id = }).FirstOrDefault()); // sql = "select Id, BId, No, Name, CreateDate from tch_teacher limit 3;"; //No这个字段, 在类中并没有
var list = conn.Query<Test>(sql);
Console.WriteLine(list.ToList().FirstOrDefault().BId); // c5f5959e-0744-42cd-a843-145e28149d9b
}
Console.ReadKey();
}
}
接下来, 可以进入Dapper的部分了
三、Dapper 开始
Query<int/string> 和 Query<Test>在读取数据的部分是一样的, 开始出现不同的地方主要体现在 object to model 的部分,
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
首先注意一个地方
static SqlMapper()
{
//这部分是 简单类型处理用到的, 当然这其中并不仅仅只有简单类型
typeMap = new Dictionary<Type, DbType>();
typeMap[typeof(byte)] = DbType.Byte;
typeMap[typeof(sbyte)] = DbType.SByte;
typeMap[typeof(short)] = DbType.Int16;
typeMap[typeof(ushort)] = DbType.UInt16;
typeMap[typeof(int)] = DbType.Int32;
typeMap[typeof(uint)] = DbType.UInt32;
typeMap[typeof(long)] = DbType.Int64;
typeMap[typeof(ulong)] = DbType.UInt64;
typeMap[typeof(float)] = DbType.Single;
typeMap[typeof(double)] = DbType.Double;
typeMap[typeof(decimal)] = DbType.Decimal;
typeMap[typeof(bool)] = DbType.Boolean;
typeMap[typeof(string)] = DbType.String;
typeMap[typeof(char)] = DbType.StringFixedLength;
typeMap[typeof(Guid)] = DbType.Guid;
typeMap[typeof(DateTime)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
typeMap[typeof(TimeSpan)] = DbType.Time;
typeMap[typeof(byte[])] = DbType.Binary;
typeMap[typeof(byte?)] = DbType.Byte;
typeMap[typeof(sbyte?)] = DbType.SByte;
typeMap[typeof(short?)] = DbType.Int16;
typeMap[typeof(ushort?)] = DbType.UInt16;
typeMap[typeof(int?)] = DbType.Int32;
typeMap[typeof(uint?)] = DbType.UInt32;
typeMap[typeof(long?)] = DbType.Int64;
typeMap[typeof(ulong?)] = DbType.UInt64;
typeMap[typeof(float?)] = DbType.Single;
typeMap[typeof(double?)] = DbType.Double;
typeMap[typeof(decimal?)] = DbType.Decimal;
typeMap[typeof(bool?)] = DbType.Boolean;
typeMap[typeof(char?)] = DbType.StringFixedLength;
typeMap[typeof(Guid?)] = DbType.Guid;
typeMap[typeof(DateTime?)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
typeMap[typeof(TimeSpan?)] = DbType.Time;
typeMap[typeof(object)] = DbType.Object;
//这个方法可以实现自定义处理, 它是一个public static 方法
AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), false);
}
然后看GetDeserializer方法
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{ // dynamic is passed in as Object ... by c# design
if (type == typeof(object)
|| type == typeof(DapperRow))
{
//object / dynamic 类型, 会执行以下方法
return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing);
} Type underlyingType = null;
if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary ||
(type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum)))
{
ITypeHandler handler;
if (typeHandlers.TryGetValue(type, out handler))
{
//自定义处理
return GetHandlerDeserializer(handler, type, startBound);
}
//复杂类型的处理
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
}
//以上简单类型, 值类型, 可空值类型, 枚举, linq的二进制 的处理
return GetStructDeserializer(type, underlyingType ?? type, startBound);
}
这里我只介绍 复杂类型的处理方式了, 至于其他的, 跟Emit的主题关系不是很大, 有兴趣的童鞋, 可以自己去看一下, 应该是能看懂的
由于 GetTypeDeserializer 这个方法实在是太长了, 我把说明都写在注释里面去吧. 按照我的注释, 应该是能看懂整个过程的. 可能还是IL那一段不太好懂, 我第一次看的时候, 就看到那里就没继续看下去了, 实在是不想继续看了. 以下是代码部分
/// <summary>
/// Internal use only
/// </summary>
/// <param name="type"></param>
/// <param name="reader"></param>
/// <param name="startBound"></param>
/// <param name="length"></param>
/// <param name="returnNullIfFirstMissing"></param>
/// <returns></returns>
public static Func<IDataReader, object> GetTypeDeserializer(
#if CSHARP30
Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
#else
Type type, IDataReader reader, int startBound = , int length = -, bool returnNullIfFirstMissing = false
#endif
)
{
//创建动态方法 Deserialize[Guid]
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true);
var il = dm.GetILGenerator();
il.DeclareLocal(typeof(int)); //定义本地变量 loc0
il.DeclareLocal(type); //定义本地变量 loc1 -> target
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0); //初始化本地变量loc0, loc0 = 0 if (length == -)
{
length = reader.FieldCount - startBound; //获取要转换字段的个数
} if (reader.FieldCount <= startBound)
{
throw MultiMapException(reader);
} //获取读取出来的字段名, 并转入数组中 -> string[] Id, BId, No, Name, CreateDate
var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray(); ITypeMap typeMap = GetTypeMap(type); //new DefaultTypeMap(type) int index = startBound; //有参构造函数
ConstructorInfo specializedConstructor = null;
//需要初始化标志
bool supportInitialize = false;
if (type.IsValueType) //target是值类型
{
il.Emit(OpCodes.Ldloca_S, (byte)); //加载loc1的地址
il.Emit(OpCodes.Initobj, type); //初始化loc1, loc1 = 0
}
else //target是引用类型
{
var types = new Type[length];
for (int i = startBound; i < startBound + length; i++)
{
//获取读到的db值的类型
types[i - startBound] = reader.GetFieldType(i);
}
//查找标记了ExplicitConstructor属性(Attribute)的构造函数
var explicitConstr = typeMap.FindExplicitConstructor();
if (explicitConstr != null)
{
#region 存在
var structLocals = new Dictionary<Type, LocalBuilder>(); var consPs = explicitConstr.GetParameters(); //获取该构造函数上的参数集 #region 遍历加载参数
foreach (var p in consPs)
{
//引用类型
if (!p.ParameterType.IsValueType)
{
//如果传入参数为复杂类型, 则以 null 来处理
il.Emit(OpCodes.Ldnull);
}
else //值类型
{
LocalBuilder loc;
if (!structLocals.TryGetValue(p.ParameterType, out loc))
{
//定义本地变量
structLocals[p.ParameterType] = loc = il.DeclareLocal(p.ParameterType);
} il.Emit(OpCodes.Ldloca, (short)loc.LocalIndex);
il.Emit(OpCodes.Initobj, p.ParameterType); //初始化传入参数, a=0,b=false之类的
il.Emit(OpCodes.Ldloca, (short)loc.LocalIndex);
il.Emit(OpCodes.Ldobj, p.ParameterType); //加载初始化后的参数
}
}
#endregion il.Emit(OpCodes.Newobj, explicitConstr); //创建对象 new target(...);
il.Emit(OpCodes.Stloc_1); //loc1 = target //target 是否实现 ISupportInitialize 接口, 如果实现, 则调用其 BeginInit 方法
supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
if (supportInitialize)
{
il.Emit(OpCodes.Ldloc_1);
il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null);
}
#endregion
}
else
{
#region 不存在
var ctor = typeMap.FindConstructor(names, types); //查找构造函数, 优先返回无参构造函数
if (ctor == null)
{
//找不到能用的构造函数
string proposedTypes = "(" + string.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")";
throw new InvalidOperationException(string.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization", proposedTypes, type.FullName));
} if (ctor.GetParameters().Length == )
{
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_1); //loc1 = new target();
supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
if (supportInitialize)
{
il.Emit(OpCodes.Ldloc_1);
il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null);
}
}
else
{
specializedConstructor = ctor;
}
#endregion
}
} //try 开始
il.BeginExceptionBlock();
if (type.IsValueType)
{
//如果是值类型, 加载target的地址
il.Emit(OpCodes.Ldloca_S, (byte));// [target]
}
else if (specializedConstructor == null) //构造函数为无参构造函数
{
//引用类型, 则直接使用变量即可
il.Emit(OpCodes.Ldloc_1);// [target]
} //用reader中的列去匹配target中的属性, 匹配不上, 则显示为null, 此处的No为null
var members = (specializedConstructor != null
? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n))
: names.Select(n => typeMap.GetMember(n))).ToList(); //无参 // stack is now [target] bool first = true;
var allDone = il.DefineLabel();
int enumDeclareLocal = -,
//定义第二个本地变量,object类型的, 然后返回此本地变量的index值, 其实就是截止目前, 定义了本地变量的个数
valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex;
foreach (var item in members)
{
if (item != null)
{
#region object to model if (specializedConstructor == null) //无参构造函数存在
il.Emit(OpCodes.Dup); // stack is now [target][target] Label isDbNullLabel = il.DefineLabel();
Label finishLabel = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
EmitInt32(il, index); // stack is now [target][target][reader][index]
il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] //loc0 = [index]
//获取reader读取的值, reader[index]
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object]
StoreLocal(il, valueCopyLocal); //将 reader[index]的值, 存放到本地变量 loc_valueCopyLocal 中 Type colType = reader.GetFieldType(index); //reader[index] 的列的类型 source
Type memberType = item.MemberType; //target[item] 的类型 target //如果目标类型为char 或者 char? , 则调用ReadChar / ReadNullableChar方法来完成转换
if (memberType == typeof(char) || memberType == typeof(char?))
{
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(
memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
}
else
{
il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object]
//判断是否为DBNull类型, 如果是, 则跳转到 标签isDbNullLabel
il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object] // unbox nullable enums as the primitive, i.e. byte etc
// int? -> int, int/string -> null, 根据可空值类型来获取其值类型
var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType; if (unboxType.IsEnum)
{
Type numericType = Enum.GetUnderlyingType(unboxType);
if (colType == typeof(string))
{
if (enumDeclareLocal == -)
{
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
}
il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string]
StoreLocal(il, enumDeclareLocal); // stack is now [target][target]
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
LoadLocal(il, enumDeclareLocal); // stack is now [target][target][enum-type][string]
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
else
{
FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType);
} if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
}
}
else if (memberType.FullName == LinqBinary)
{
il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
}
else
{
TypeCode dataTypeCode = Type.GetTypeCode(colType),
unboxTypeCode = Type.GetTypeCode(unboxType);
bool hasTypeHandler;
if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
{
//判断是否有自定义的转换方法, 如果有, 则调用自定义的方法完成转换
if (hasTypeHandler)
{
#pragma warning disable 618
il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod("Parse"), null); // stack is now [target][target][typed-value]
#pragma warning restore 618
}
else
{
//将指令中指定类型的已装箱的表示形式转换成未装箱形式
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
}
else
{
// not a direct match; need to tweak the unbox
FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null);
if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
}
}
}
}
if (specializedConstructor == null)
{
// Store the value in the property/field
if (item.Property != null)
{
if (type.IsValueType)
{
il.Emit(OpCodes.Call, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
}
else
{
il.Emit(OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
}
}
else
{
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
}
} il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
if (specializedConstructor != null)
{
il.Emit(OpCodes.Pop);
if (item.MemberType.IsValueType)
{
int localIndex = il.DeclareLocal(item.MemberType).LocalIndex;
LoadLocalAddress(il, localIndex);
il.Emit(OpCodes.Initobj, item.MemberType);
LoadLocal(il, localIndex);
}
else
{
il.Emit(OpCodes.Ldnull);
}
}
else
{
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Pop); // stack is now [target]
} if (first && returnNullIfFirstMissing)
{
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldnull); // stack is now [null]
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Br, allDone);
} il.MarkLabel(finishLabel);
#endregion
} first = false;
index += ;
}
if (type.IsValueType)
{
il.Emit(OpCodes.Pop);
}
else
{
//构造函数为有参的构造函数
if (specializedConstructor != null)
{
//创建对象
il.Emit(OpCodes.Newobj, specializedConstructor);
}
il.Emit(OpCodes.Stloc_1); // stack is empty //实现 ISupportInitialize 接口, 调用 EndInit 方法, 完成初始化
if (supportInitialize)
{
il.Emit(OpCodes.Ldloc_1);
il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("EndInit"), null);
}
}
il.MarkLabel(allDone);
//try 结束 -> catch 开始
il.BeginCatchBlock(typeof(Exception)); // stack is Exception
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
LoadLocal(il, valueCopyLocal); // stack is Exception, index, reader, value
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null); //抛出异常
il.EndExceptionBlock();
//catch 结束 il.Emit(OpCodes.Ldloc_1); // stack is [rval] 此处就是转换后的最终结果
if (type.IsValueType)
{
il.Emit(OpCodes.Box, type);
}
il.Emit(OpCodes.Ret); return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
}
其中的value-as-object是从reader中读取出来的未转换的数据, typed-value是转换后的数据
本想做成可收缩的, 但是那种展开后, 不能点, 只要一点, 就自动收起来了, 感觉不方便, 所以还是贴出来了
其中还有些地方不够详细, 不过对照着这个, 去看Dapper源码, 是可以看的懂了
在下一篇中, 我会画出堆栈中的变化, 来减少理解难度
Emit学习(4) - Dapper解析之数据对象映射(一)的更多相关文章
- Emit学习(4) - Dapper解析之数据对象映射(二)
承接着上一篇, 这一篇主要以堆栈的方式来演示一下, db数据转换到类中去的一个过程. 一.先看第一张图 程序在运行到176行(上一篇贴出的代码)的时候, 就会出现上图中的第一个栈. 那在此之前, Da ...
- php设计模式 数据对象映射模式
数据对象映射模式,是将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作. 在代码中实现数据对象映射模式,实现一个ORM类,将复杂的sql语句映射成对象属性的操作.对象关系映射(Obje ...
- PHP 设计模式 笔记与总结(10)数据对象映射模式 2
[例2]数据对象映射模式结合[工厂模式]和[注册模式]的使用. 入口文件 index.php: <?php define('BASEDIR',__DIR__); //定义根目录常量 includ ...
- PHP 设计模式 笔记与总结(9)数据对象映射模式
[数据对象映射模式] 是将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作.例如在代码中 new 一个对象,使用数据对象映射模式就可以将对象的一些操作比如设置一些属性,就会自动保存到数 ...
- PHP设计模式笔记六:数据对象映射模式 -- Rango韩老师 http://www.imooc.com/learn/236
数据对象映射模式 1.数据对象映射模式,是将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作 2.在代码中实现数据对象映射模式,我们将实现一个ORM类,将复杂的SQL语句映射成对象属性 ...
- ASP.NET MVC 模型和数据对象映射实践
在使用 MVC 开发项目的过程中遇到了个问题,就是模型和数据实体之间的如何快捷的转换?是不是可以像 Entity Framework 的那样 EntityTypeConfiguration,或者只需要 ...
- .NetCore学习笔记:四、AutoMapper对象映射
什么是AutoMapper?AutoMapper是一个简单的小型库,用于解决一个看似复杂的问题 - 摆脱将一个对象映射到另一个对象的代码.这种类型的代码是相当沉闷和无聊的写,所以为什么不发明一个工具来 ...
- Python学习笔记_Chapter 6定制数据对象
1. 有用的BIF a. 判断字符串中是否包含子字符串 if s_a in s_b: b. pop() 描述:从指定的列表位置删除并返回一个数据项. (sarah_name,sarah_dob)=l_ ...
- 一文为你详细讲解对象映射库【AutoMapper】所支持场景
前言 在AutoMapper未出世前,对象与对象之间的映射,我们只能通过手动为每个属性一一赋值,时间长了不仅是我们而且老外也觉得映射代码很无聊啊.这个时候老外的所写的强大映射库AutoMapper横空 ...
随机推荐
- Boyer-Moore 字符串匹配算法
字符串匹配问题的形式定义: 文本(Text)是一个长度为 n 的数组 T[1..n]: 模式(Pattern)是一个长度为 m 且 m≤n 的数组 P[1..m]: T 和 P 中的元素都属于有限的字 ...
- 推荐一些常用感觉不错的jQuery插件
转:http://www.cnblogs.com/v10258/p/3263939.html JQuery插件繁多,下面是个人在工作和学习中用到感觉不错的,特此记录. UI: jquery UI(官方 ...
- 剑英陪你玩转图形学(五)focus
很久没来和大家交流业务(zhuangbi)水平了,最近实在是很忙,报名了小游戏大赛,一点时间都抽不出,已经坑了. 今天抓紧时间和大家介绍一个小效果: 新手引导的时候,我们会需要一种全屏幕黑掉,只有一个 ...
- Unity3d热更新全书-加载(二)如何在不用AssetBundle的前提下动态加载预设
Unity3D的主要构成大家都知道,首先是场景图,场景图上的节点构成一颗树. 每个节点对应一个GameObject对象 然后每个GameObject有若干个组件 有一些组件会与资源产生关系,比如Mes ...
- 科蓝软件急招前端开发、PHP、.NET工程师
职位:前端开发 工作年限:不限 学历要求:大专 招聘人数:2 专业:不限 薪酬:面议 工作地点:浙江嘉兴.北京 岗位职责: 1.负责公司项目的UI设计: 2.负责将UI静态化 ...
- 爱上MVC3~MVC+ZTree实现对树的CURD及拖拽操作
回到目录 上一讲中,我们学习了如何使用zTree对一棵大树(大数据量的树型结构的数据表,呵呵,名称有点绕,但说的是事实)进行异步加载,今天这讲,我们来说说,如何去操作这棵大树,无非就是添加子节点,删除 ...
- excel怎么固定第一行
这里给大家介绍一下怎么固定表格的第一行,或者说怎么固定表格的表头. 1.我这里有一个成绩表,希望固定住其第一行. 2.选择单元格A2 注意:你只需要选择所要固定行的下一行的任一单元格即可!!! 3.然 ...
- Java线程:线程栈模型与线程的变量
Java线程:线程栈模型与线程的变量 要理解线程调度的原理,以及线程执行过程,必须理解线程栈模型. 线程栈是指某时刻时内存中线程调度的栈信息,当前调用的方法总是位于栈顶.线程栈的内容是随着程序的运 ...
- salesforce 零基础学习(三十三)通过REST方式访问外部数据以及JAVA通过rest方式访问salesforce
本篇参考Trail教程: https://developer.salesforce.com/trailhead/force_com_dev_intermediate/apex_integration_ ...
- session 学习
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息. 当程式需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里 ...