摘要:本文主要向大家介绍了C#编程之C#中的LINQ,通过具体的内容向大家展示,希望对大家学习C#编程有所帮助。
本文主要向大家介绍了C#编程之C#中的LINQ,通过具体的内容向大家展示,希望对大家学习C#编程有所帮助。
从自己的印象笔记里面整理出来,排版欠佳。见谅!
1、LINQ: 语言集成查询(Language Integrated Query)
实例:
var q=
from c in categories
join p in products on c equals p.Category into ps
select new{Category=c, Products=ps};
2、LINQ 类型
LINQ to Objects(或称LINQ to Collection),这是LINQ 的最基本功能,针对集合对象进行查询处理,包括基本的汇总与过滤都在这个功能内实现。
LINQ to SQL,这是LINQ 功能的SQL Server 数据库版本,通过LINQ 指令,可以直接查询SQL Server 数据库,而完全无须编写SQL 指令。
LINQ to XML,是针对XML 设计的LINQ 功能,它通过XDocument 与XElement 两个主要类的功能,进行LINQ 语法解析与XML 内的元素的查询操作。可用来替代现有以XPath方式解析XML文件的功能。
LINQ to DataSet(或称LINQ to ADO.NET),是为现有以DataSet或DataTable 对象开发应用程序提供支持LINQ操作的功能,以AsEnumerate() 为基础,将DataSet与DataTable内的数据转换成IEnumerable接口的操作集合,即可直接使用LINQ to Objects 的方式查询。
3、LINQ 的基础
LINQ本身以IEnumerable
LINQ 本身的基础建设实现与System.Linq 命名空间内,若没有使用using 引入这个命名空间的话,所有LINQ 功能都无法使用。
3.1、扩展方法
在System.Linq 命名空间内有一个叫做Enumerable 的静态类,这个类实现了许多的方法,而且这些方法都可以在任何实现IEnumerable
要实现扩展方法十分简单,首先决定要为哪个类实现扩展方法,接着建立一个静态类(static class),名称建议使用“要扩展的类名称”+Extension 字样,例如像扩展Int 方法,就将类命名为Int32Extension;要扩展DateTime,就命名为DateTimeExtension。接下来在类内加入要扩展的方法,但要注意两件事:
(1)必须是静态方法,且名称不可以和现有的方法冲突。
(2)至少要有一个扩展类型输入参数,扩展类型输入参数格式必须是“this[要扩展的类名称][参数名称]”,若有两个以上的参数,则扩展类型输入参数必须放在第一个,且不能设置默认值。
下面是一个例子,想要扩展int 和double 两个对象,分为赋予产生货币字符串与百分比字符串的功能,按照扩展方法的规则,先定义Int32Extension 与DoubleExtension 两个类,并声明为static class
public static class Int32Extension
{
public static string FormatForMoney(this int Value)
{
return Value.ToString("$###,###,###,##0");
}
}
public static class DoubleExtension
{
public static string FormatPercent(this doubel Value)
{
return Value.ToString("0.00%");
}
}
using System;
namespace ExtensionTest
{
class Program
{
static void Main(string[] args)
{
int money = 12456789;
double p = 0.123456;
Console.WriteLine("{0}",money.FormatForMoney());
Console.WriteLine("{0}", p.FormatPercent());
}
}
}
若要使用不同命名空间内的扩展方法,记得要加上命名空间声明(使用using),否则编译器会找不到扩展方法的类。
3.2、匿名类型与对象初始化器
在开头的实例中的完整例子是这样的:
var studentScoreQuery=
from student in students
join csScore in csScores on student.ID equals csScore.ID
join dbScore in dbScores on student.ID equals dbScore.ID
select new
{
ID=student.ID,
Name=student.Name,
ScoreSum=csScore.Score+dbScore.Score,
ScoreAvg=(csScore.Score+dbScore,Score)/2
};
语法中有一个select new ,可以按做设置的属性自动产生类对象,并且自动赋予数值,这个语法包含了两个语言功能:对象初始化器与匿名类型。
对象初始化器(object initializer)允许在程序中通过声明的方式直接给对象属性进行数值的初始化,而不必刻意有参数的构造函数,可降低程序员维护多个构造函数的负担,
private static List
{
return new List
new Student(){
ID="001",Name="张三"
},
new Student(){
ID="002",Name="李四"
}
});
}
Person jeff=new Person("Jeff"){Age="20"}
List
{
new Cat(){Name="1",Age=1},
new Cat(){Name="2",Age=2}
};
Dictionary
{
{111,new Student{FirstName="1",LastName="2"}},
{112,new Student{FirstName="2",LastName="3"}}
}
声明匿名类型数组
var o=new
{
name="123",
value="456"
};
var o1=new []{
new {name="123",value="456"},
new {name="222",value="ddd"}
};
匿名类型的限制:
匿名类型一般只会用在用一个函数内,如果要让它被其他函数共享,则必须要动用到Reflection ,或是利用 .NET4.0 提供的动态类型(dynamic types)机制。
匿名类型只能有属性,不可以有方法、事件或字段等。
两个匿名类型对象的相等(Equal),必须要另个对象的属性值都相等才行。
匿名类型的初始化只能利用对象初始化器来进行,其属性在生成后会变成只读。
Var 类型的限制:
1、使用var类型时复制语句的右边不可以是null,否则编译器无法推断出其类型。
2、var 类型只能用于局部变量的声明,不能用于全局变量、类层级的变量或是函数的返回值。
3、var 类型不可用在匿名委派或是方法群组中。
3.3、yield 指令与延迟查询
yield 指令,它可以让程序员以只传回每个元素的方式来自动生成 IEnumerable
private static IEnumerable
{
List
for(int i=1;i<=5;i++)
{
list.Add(i);
}
return list;
}
private static IEnumerable
{
for(int i=1;i<=5;i++)
{
yield return i;
}
}
private static IEnumerable
{
foreach(var number in NumberSeries)
{
if(number>100)
yield break;
else
yield return number;
}
}
yield 指令的用途其实并不单单只是精简写法, yield 指令也是一种编译器魔法,会在程序编译的同时,使用yield 指令的程序段生成迭代运算的有限状态机(state machine)。这个状态机监控对象在“巡航”时所作的访问动作,在每一次“巡航”调用触发时才会真正进入集合获取数据,这个特性在LINQ 中相当重要,因为LINQ 不只是想支持集合对象,还要支持外部数据源。若没有这个特性,当LINQ 对外部数据源进行查询时,要一次取回所有数据,而无法只针对程序的需要来获取数据,这样不但会造成时间上的浪费(抓取所有数据所需的网络传输时间),也会造成空间上的浪费。所以LINQ 在设计时使用这个机制,让对集合对象的访问推迟到真正查询时才触发,这个机制称为延迟查询(Deferred Query)或延迟执行(Deferred Execution)。
3.4、Fluent Interface
Enumerbale 内所包含的LINQ 扩展方法,都返回IEnumerable
通过调用方法来定义对象内容。
对象会自我引用(self-referential),且新的对象内容会和最后一个对象内容等价。
通过返回void 内容(就是null 或不返回最后的对象内容)或非Fluent Interface 的对象结束。
可以直接对集合进行两次过滤:
var query=list.Where(c=>c<10000).where(c=>c>1000);
或是提取需要的数据结构
var query=list.Where(c=>c<10000).select(c=>new {id=c});
4、LINQ 语句
实例:查询单价1000的商品:
var query=from product in db.GetProducts()
where product.Price>1000
select product;
实例:查询商品目前的销售数字:
var query=from o in db.GetOrderDetails()
group by o.ProductID into g
select new{
ProductID=g.Key,
Qty=g.Sum(p=>p.ProductID)
};
实例:查询两个集合内都有数字的LINQ 语句:
List
List
var query=from item1 in list1
join item2 in list2 on item1 equals item2
select item2;
foreach(var q in query)
Console.Write("{0}",q);
1、LINQ 函数
1.1、查询结果过滤 :where()
Enumerable.Where() 是LINQ 中使用最多的函数,大多数都要针对集合对象进行过滤,因此Where()在LINQ 的操作上处处可见,Where()的主要任务是负责过滤集合中的数据:其原型如下:
public static IEnumerbale
public static IEnumerable
Where() 的参数是用来过滤元素的条件,它要求条件必须传回bool,以确定此元素是否符合条件,或是由特定的元素开始算起(使用Func
List
list1.Where(c=>c>5);
或者
list1.Where(c=>c>=1).Where(c=>c<=5);
list1.Where(c=>c>=1&&c<=5);
Where() 的判断标准是,只要判断函数返回true 就成立,反之则取消。
1.2、选取数据: Select()、SelectMany()
通常在编写LINQ 函数调用时较少用到选取数据的函数(因为函数调用会直接返回IEnumerable
public static IEnumerable
public static IEnumerable
与Where() 类似,Select() 也可以按照元素所在的位置判断处理,而Select()所指定的处理式selector 必须传回一个对象,这个对象可以是现有的类型,也可以是匿名的类型,既可以通过Select() 来重新组装所需数据。例:
var query=db.OrderDetails.Where(o=>o.ID==12345).Select(o=>new{ ProductID=o.ProductID,Qty=o.Qty});
Select() 的另一个相似函数SelectMay() 则是处理有两个集合对象来源的数据选取,其原型如下:
public static IEnumerable
public static IEnumerable
public static IEnumerable
public static IEnumerable
SelectMany() 在LINQ 函数调用上较难理解,但如果把它想象成数据库的CROSS JOIN ,相对来说就容易懂了,例:
List
List
var query=list1.SelectMany(o=>list2);
foreach(var q in query)
Console.WriteLine("{0}",q);
输出结果:
6424790642790642790642790642790642790
因为“642790”输出了 6次,list1 内的元素是6个,所以可以知道SelectMany() 会按照list1 内的元素个数调用它的selector,并组装集合输出。
1.3、群组数据:GroupBy()、ToLookup()
汇总数据是查询机制的基本功能,而在汇总之前,必须要先将数据做群组化,才能进行统计,LINQ 的群组数据功能由Enumerable.GroupBy()函数提供。
GroupBy() 会按照给定的key(keySelector)以及内容(elementSelector),产生群组后的结果(IGroup 接口对象或是由resultSelector生成的结果对象),例:
List
var group=sequence.GroupBy(o=>o);
foreach(var g in group)
{
Console.WrilteLine("{0} count:{1}",g.Key,g.Count());//计算每个数出现的次数。
GroupBy 设置了使用数列本身值作为Key值,并且利用这个Key 分组产生分组的数据(IGrouping<TKey,TElement类型),再对分组的数据进行汇总。结果如下:
1 count: 1
2 count: 3
3 count: 2
4 count: 4
6 count: 1
若是想要在返回之前对分组后的元素做处理,可以传入elementSelector 而若是要在元素处理后产生结果的话,则可以传入resultSelector,这样返回的集合会是以resultSelector 返回的类型为主,而不是默认的IGroup接口。
除了GroupBy()能群组化数据外、另外一个具有群组化数据能力的是ToLookUp(),它可以生成具有群组化特性的集合对象,由ILookup
ToLookup()看起来和GroupBy()有些类似,但是它会另外生成一个新的集合对象,这个集合对象由ILookup
var nameValuesGroup=new[]
{
new{name="Allen", value=65,group="A"},
new{name="Abbey",value=120,group="B"},
new{name="Sue",Value=200,group="A"}
};
var lookupValues=namValuesGroup.ToLookup(c=>c.group);
foreach(var g in lookupValues)
{
Console.WriteLine("===Group: {0}===",g.Key);
foreach(var item in g)
{
Console.WriteLine("name:{0},value:{1}",item.name,item.value);
}
}
GroupBy()本身具有延迟执行的特性,而ToLookup()没有。
1.4、联接数据: Join() 与GroupJoin()
身为一个查询机制,将两个集合进行联接(join)也是理所当然的,尤其是在进行数据的对比和汇总时,联接机制显得更重要。在LINQ 函数中,有Enumerable.Join() 函数负责处理联接,其原型如下:
public static IEnumerable
....
由原型可看到它将原本的集合视为TOuter,而将传入的集合视为TInner,儿还要决定由哪个属性或成员当Key,最后由resultSelector 来输出联接的结果。例:
var query=from item1 in list1
join item2 in list2 on item1 equals item2
select item2;
var query3=list1.Join(
list2,
item1=>item1,
item2=>item2,
(item1,item2)=>item2
);
Enumerable
目前常用的联接模式,INNER JOIN由 Enumerable
GroupJoin 和 Join() 十分相似,不过它却又Join() 和 GroupBy() 两者的功能,在Join() 的情况下,它会留下TInner 和TOuter 两边都有的值,但在GroupJoin() ,它会将TOuter 的值作为Key,并依此来对TInner 做群组化后输出,例:
var query4=from item1 in list1
join item2 in list2 on item1 equals item2 into g
from item in g.DefaultIfEmpty()
select new{ v=item1,c=item};
var query5=list1.GroupJoin(
list2,
item1=>item1,
item2=>item2,
(item1,item2)=>new {v=item1,c=item2.Count()});
1.5、数据排序:OrderBy() 与ThenBy()
数据排序是在数据处理中常见的功能,在LINQ 内的排序主要是以OrderBy 函数为主,而为了支持连续条件的排序,可加上ThenBy 函数,以便处理多重条件排序的需求。基于LINQ的延迟查询机制,排序也不是在一开始就进行的,而是在数据真的被访问时才会进行排序。因此OrderBy()在处理集合时,传递回来的是称为IOrderedEnumerable
OrderBy和ThenBy还有一个相似的方法,差别只在于做反向排序。OrderByDescending 和ThenByDescending。
观察函数的原型,会发现OrderBy传入的是IEnumerable
var nameValues=new[]
{
new {name="Allen",value=64},
new {name="abbey",value=120},
new {name="slomng",value=330},
new {name="george",value=213}
};
//single sort
var sortedNames=nameValues.OrderBy(c=>c.name);
var sortedValues=nameValues.OrderBy(c=>c.value);
//multiply sort conditions
var sortedByNameValues=nameValues.OrderBy(c=>c.name).ThenBy(c=>c.value);
var sortedByValueNames=nameValues.OrderBy(c=>c.value).ThenBy(c=>c.name);
如果要设置多重排序条件,请务必使用OrderBy()加上ThenBy()的组合,若使用OrderBy +OrderBy 组合,会使得排序被执行两次,最终的结果会是最后一个OrderBy 所产生的的结果。
1.6、获取集合
LINQ 所处理的数据都由集合而来,因此将LINQ 执行的结果转换成集合也很容易。LINQ本身支持四种不同的集合生成方式,包含生成数组的ToArray()、生成列表的ToList、生成字典集合的ToDictionary 以及生成Lookup
var arrayOutput=nameValues.ToArray();
var listOutput=nameValues.ToList();
var dictOutput1=nameValues.ToDictionary(c=>c.name);
var dictOutput2=nameValues.ToDictionary(c=>c.name,c=>value);
1.7、划分并获取集合
Skip()、 SkipWhile()、 Take()、 TakeWhile()。在数据库查询时,为了达到最佳的性能,在数据量大时要进行分页处理(paging)。上面四个函数的功能就是在大集合内切出少量数据。
public static IEnumberable
this IEnumerable
int count
)
public static IEnumberable
this IEnumerable
Func
)
public static IEnumberable
this IEnumerable
Func
)
public static IEnumberable
this IEnumerable
int count
)
public static IEnumberable
this IEnumerable
Func
)
public static IEnumberable
this IEnumerable
Func
)
Skip()用来在集合中跳跃,让LINQ 核心直接将游标跳到指定的位置,而不用通过“巡航”来移动,在大型集合中可节省不少时间,而SkipWhile 也有相同作用,但多了判断式,也就是跳过符合条件的元素,而不同的SkipWhile()可用来决定要跳过符合条件的或是判断跳过特定的索引值。
Take()用来传回集合中特定数量的元素,它会告知LINQ 核心直接返回它所指定的元素数量,很适合使用与分页的功能。TakeWhile 则是和SkipWhile 类似都是多了条件判断式,不过TakeWhile 在元素满足条件时,就返回该元素或是符合特定的索引值条件时返回该元素。
1.8、访问元素
IEnumerable
首先是获取首尾的元素,分别由First() 以及Last() 两个方法负责,它们还各有一个姐妹方法 FirstOrDefault() 以及 LastOrDefault() 前者若没有第一个或最后一个元素时,会传回null,而后者会传回其类型的默认值(基本上就是default(T)的结果)。
FirstOrDefault() 以及 LastOrDefault()都没有提供默认的设置方式,因此若想要使用非default(T)的默认值,要使用DefaultEmpty() 来设置。First() 和Last() 都能传入判断元素是否符合条件的参数,当条件判断存在时,First 会从集合的前面开始扫描,并返回扫描到符合条件的第一个元素,Last 则是反过来从集合的尾端开始扫描,并返回扫描到符合条件的第一个元素。例:
var firstLastItems=new []{"zero","two","three","four","five"};
string firstContainsO=firstLastItems.First(s=>s.Contains(‘o‘));
string lastContainsO=firstLastItems.Last(s=>s.Contains(‘0‘));
LINQ 内还有一个Single,他会在集合中只有一个元素时传回该元素,但若集合是空的或是有两个以上的元素时会调用例外处理,或是使用它的姐妹方法SingleOrDefault 传回null值,实用性比fisrt和last 低。
LINQ 提供了ElementAt() 这个方法,可按照索引值访问元素,他有个相似方法ElementAtOrDefault 作用和firstordefault/lastordefault 是相同的。当找不到元素时就返回默认值。例:
var firstLastItems=new []{"zero","two","three","four","five"};
string itematThree=firstLastITems.ElementAt(2);
若要判断集合内有没有特定值,LINQ 提供了Contains, 可以判断集合捏有没有传入的元素,但因为Contain 会判断对象是否相等,所以它另外提供了一个可传入IEqualityComparer
若要判断集合内有没有值,LINQ 提供了两个方法,一个是Count(), 另一个是Any(),除了可以简单判断集合内有没有值外,也可以传入判断条件来决定是否要列入计算。通常会习惯使用Count 来判断集合内是否存在任何元素,为什么要多做一个Any 呢。其实是考虑到LINQ 可能的查询对象会包含远程数据库,不一定只有本地的数据源。对于远程的数据源,如果使用Count ,要花费较高的成本来读取数据后进行计数在传回,但若是使用Any(),则远程只要判断符合条件的数据是否存在一笔即可,不需要完整计数,所以针对远程数据源,使用Any 来判断有无数据是较好的选择。针对本地的集合 any 和count 几乎没有差异。
若要判断集合内的元素是否全部符合特定条件时, 可以利用LINQ 的All(), 它可以按照传入的条件来扫描所有元素,只有在所有元素都符合条件时,或是集合时空时才会返回true ,否则会返回false。
若要按照元素的类型进行筛选的话,除了使用Where 对每个元素做类型信息判断外,LINQ 也提供了一个更简便的方法 OfType
OfType
1.9、聚合与汇总
聚合运算(aggregation)是集合数据处理的重要功能之一,基本的Max ,Min ,Sum , Average 以及可自己制定聚合规则的Aggregate()。
Aggregate 是可暂存每一步计算结果的方法,它允许程序员按照传入的条件对每个集合内的元素进行计算,而在每次调用时,他都会将前一次的结果暂存起来,并作为下次计算的传入参数。Aggregate 基本上做到三种工作,第一种是直接按照传入的条件来处理累计运算;第二种是可在调用时传入一个种子值(seed),这个种子值会在开始进行运算时作为基准使用,之后可按照第一次对种子值的运算方式开始做累计运算;第三种则是在传回之前做最后的处理,例:
double myBalance=100.0;
int[] withdrawItems={20,10,40,50,10,70,30};
double balance=withdrawItems.Aggregate(myBalance,(originbalance,nextWithdrawal)=>{
Console.WriteLine("originbalance:{0},nextWithdrawak:{1}",originbalance,nextdrawal);
Console.WriteLine("Withdrawal status:{0}",(nextWithdrawal<=originbalance)>"OK":"FAILED");
return ((nextWithdrawal<=originbalance)?(originbalance-nextWithdrawal):originbalance);
});
Console.WriteLine("Ending balance:{0}:",balance);
若要岁最终的存款值进行处理,即可使用第三个参数resultSelector,例:
var balanceStatus=
withdrawItems.Aggregate(myBalance,(originbalance,nextWithdrawal)=>{
return((nextWithdrawal<=originbalance)?(originbalance-nextWithdrawal):originbalance);
},
(finalbalance)=>
{
return (finalbalance>=1000)?"Normal":"Lower";
});
1、远程查询:IQueryable
在前面讨论的LINQ 中所有的功能,基本上都基于IEnumerable
为了达到这个需求,微软在LINQ 内建立了一个介于IEnumerable
当用户对IQueryable
LINQ 中两个特别方法:AsEnumerable 与AsQueryable 。 AsEnumerable 是将IQueryable
2、Expression
Expression 的功能实现于System.Linq.Expression 命名空间中,它记录了Expression表达式的结构,并提供了充分的信息让IQueryProvider 的开发人员访问其语法结构,以产生相对应的查询指令。可以把Expression表达式树的功能想象成一棵树,这棵树内包含了要运算的节点以及运算的方法(Expression Tree),而求解表达式时,只要使用中序遍历的方法,即可求解。
Expression 也是相同的原理,在使用IQueryable
3、LINQ 与ADO.NET : DataSet/DataTable 的使用
首先是DataTable,这里最常见的方法是 DataTable.Select(),然后用它规定的查询方式进行处理。CopyToDataTable
DataSet ds=new DataSet();
ds.Locale=CultureInfo.InvariantCulture;
FillDataSet(ds);
DataTable orders=ds.Tables["SalesOrderHeader"];
IEnumerable
from order in orders.AsEnumerable();
where order.Field
select order;
DataTable boundTable=query.CopyToDataTable
bindingSource.DataSource=boundTable;
下面介绍DataRow 本身,在一开始DataRow 基本上提供松散类型的数据容器,因此在处理查询时都要先转换才能查询,LINQ 推出后,对DataRow类型进行了扩展,扩展的功能是强化它的强类型功能,以作为查询时简化类型转换之用,它们分别是Field
DataSet ds=new DataSet();
ds.Locale=CultureInfo.InvariantCulture;
FillDataSet(ds);
DataTable products=ds.Tables["Product"];
var query=
from product in products.AsEnumerable()
where product.Field
select new
{
Name=product.Field
ProductNumber=product.Field
ListPrice=product.Field
};
foreach(var product on query)
{
Console.WirteLine("Name:{0}",product.Name);
.....
}
除了以强类型访问DataRow 的字段外,微软也提供了强类型比较的DataRowComparer类,以处理当使用强类型方式查询DataRow 使所需要的类型转换处理工作。
var contacts=contacts1.AsEnumerable().Intersect(contacts2.AsEnumerable(),DataRowComparer.Default);
MVC 5 网站开发之美笔记 LINQ:推迟查询的执行
1、推迟查询的执行
在运行期间定义查询表达式时,查询就不会运行,查询会在迭代数据项时运行。
using System;
using System.Collections.Generic;
using System.Linq;
namespace OKITEST
{
class Program
{
static void Main(string[] args)
{
var names = new List
var namesWithJ = from n in names
where n.StartsWith("J")
orderby n
select n;
Console.WriteLine("First iteration");
foreach(var name in namesWithJ)
{
Console.WriteLine(name);
}
Console.WriteLine();
names.Add("John");
names.Add("Jim");
names.Add("Jimmy");
Console.WriteLine("Second iteration.");
foreach(var name in namesWithJ)
{
Console.WriteLine(name);
}
}
}
}
运行结果如下:
因为迭代在查询定义时不会进行,而是在执行每个foreach 语句时进行,所以可以看到如上的结果。
当然,还需要注意,每次在迭代中使用查询时,都会调用扩展方法。在大多数情况下,这是非常有效的,因为我们可以检测出元数据中的变化。但是在一些情况下,这是不可行的,调用扩展方法ToArray(),ToList()等可以改变这个操作。
在下面的例子送,ToList 遍历集合。返回一个实现了IList
using System;
using System.Collections.Generic;
using System.Linq;
namespace OKITEST
{
class Program
{
static void Main(string[] args)
{
var names = new List
var namesWithJ = (from n in names
where n.StartsWith("J")
orderby n
select n).ToList();
Console.WriteLine("First iteration");
foreach(var name in namesWithJ)
{
Console.WriteLine(name);
}
Console.WriteLine();
names.Add("John");
names.Add("Jim");
names.Add("Jimmy");
Console.WriteLine("Second iteration.");
foreach(var name in namesWithJ)
{
Console.WriteLine(name);
}
}
}
}
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C#.NET频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号