提示51. 怎样由任意形式的流中加载EF元数据

在提示45中我展示了怎样在运行时生成一个连接字符串,这相当漂亮。

其问题在于它依赖于元数据文件(.csdl .ssdl .msl)存在于本地磁盘上。

但是如果这些文件存在于web服务器中或者类似的位置,甚至你无权访本地文件系统而无法把它们拷贝到本地呢?

原来你也可以由流中加载元数据,这篇提示将告诉你怎么做。

步骤1:获得用于CSDL,MSL与SSDL的XmlTextReaders:

这可以尽可能的简单,如‘new XmlTextReader(url)’。

但是在这个例子中我准备展示给你怎样由字符串变量来完成这个操作,当然这个字符串你可以由任意地方得到:

1 string csdl = "…";
2 string ssdl = "…";
3 string msl = "…";
4 var csdlReader = new StringReader(csdl);
5 var ssdlReader = new StringReader(ssdl);
6 var mslReader = new StringReader(msl);
7 var csdlXmlReader = new XmlTextReader(csdlReader);
8 var ssdlXmlReader = new XmlTextReader(ssdlReader);
9 var mslXmlReader = new XmlTextReader(mslReader);

步骤2:创建元数据ItemCollections:

接下来你需要为CSDL准备一个EdmItemCollection,为SSDL准备一个StoreItemCollection以及为MSL准备一个StorageMappingItemCollection:

 1 var edmItemCollection = new EdmItemCollection(
2 new[] { csdlXmlReader }
3 );
4 var storeItemCollection = new StoreItemCollection(
5 new[] { ssdlXmlReader }
6 );
7 var storeMappingItemCollection =
8 new StorageMappingItemCollection(
9 edmItemCollection,
10 storeItemCollection,
11 new [] { mslXmlReader }
12 );

远程还感兴趣的唯一对象就是StorageMappingItemCollection,与MSL一起,需要其它ItemCollection来验证映射。

步骤3:创建一个MetadataWorkspace:

接下来你需要将这些ItemCollection组合到一个MetadataWorkspace:

1 var mdw = new MetadataWorkspace();
2 mdw.RegisterItemCollection(edmItemCollection);
3 mdw.RegisterItemCollection(storeItemCollection);
4 mdw.RegisterItemCollection(storeMappingItemCollection);

步骤4:创建一个EntityConnection:

最后我们需要一个EntityConnection。要创建一个EntityConnection我们需要一个本地数据库连接 – 一般情况下这是一个SqlConnection,但是因为EF有一个提供程序模型,这个连接也可以为其它如Oracle数据库的连接字符串:

1 var sqlConnectionString = @"Data Source=.\SQLEXPRESS;Ini…";
2 var sqlConnection = new SqlConnection(sqlConnectionString);
3 var entityConnection = new EntityConnection(
4 metadataWorkspace,
5 sqlConnection
6 );

步骤5:创建ObjectContext并按需求使用

最后一步使用你刚创建的EntityConnection来构造你的ObjectContext,并像一般情况一样使用:

1 using (var ctx = new ProductCategoryEntities(entityConnection))
2 {
3 foreach (var product in ctx.Products)
4 Console.WriteLine(product.Name);
5 }

就是这样…不一定简单,一旦你了解怎样做了也算简单。

提示52. 怎样在数据服务客户端中重用类型

默认情况下,当你添加一个数据服务的引用后你会得到自动生成的代码,其中包含一个强类型的DataServiceContext及所有ResourceType的类。

点击“显示所有文件”可以看到项目中这个自动生成的代码:

然后展开你的数据服务的引用,再展开从属的Reference.datasvcmap,然后打开Reference.cs文件:

步骤1 – 关闭代码生成

如果你要重用一些已存在的类,你需要关闭代码生成。

这相当容易 – 选择Reference.datasvcmap文件进入它的属性,然后清空Custom Tool属性,其默认值为‘DataServiceClientGenerator’。

步骤2 – 创建一个强类型的DataServiceContext:

当我们关闭了代码生成,我们也失去了使编程更方便的强类型的DataServiceContext。

你可以像这样编写自己的DataServiceContext,代码相当容易:

 1 public class SampleServiceCtx: DataServiceContext
2 {
3 public SampleServiceCtx(Uri serviceRoot) : base(serviceRoot) {
4 base.ResolveName = ResolveNameFromType;
5 base.ResolveType = ResolveTypeFromName;
6 }
7 protected Type ResolveTypeFromName(string typeName)
8 {
9 if (typeName.StartsWith("Sample."))
10 {
11 return this.GetType().Assembly.GetType(
12 typeName.Replace("Sample.", "Tip52.")
13 false);
14 }
15 return null;
16 }
17 protected string ResolveNameFromType(Type clientType)
18 {
19 if (clientType.Namespace.Equals("Tip52"))
20 {
21 return "Sample." + clientType.Name;
22 }
23 return null;
24 }
25 public DataServiceQuery<Product> Products {
26 get {
27 return base.CreateQuery<Product>("Products");
28 }
29 }
30 }

注意Product属性简单返回DataServiceQuery<Product>,其中Product是我们试图重用的类型。

使这工作的关键是由数据服务Resource typeName映射到一个客户端Type的代码,反之亦如此。

这个映射由两个函数来控制,我们在构造函数中告诉DataServiceContext这两个函数。你可以看到在这个例子中我们只是简单的由客户端的’Tip52’的命名空间到服务器端’Sample’这个命名空间。

步骤3 – 试一试:

一旦你建立了解析器,你就可以很容易的重用已存在的类型:

1 var root = new Uri("http://localhost/Tip52/sample.svc");
2 var ctx = new SampleServiceCtx(root);
3 foreach (Product p in ctx.Products)
4 {
5 Console.WriteLine("{0} costs {1}", p.Name, p.Price);
6 p.Price += 0.30M; // Cross the board price increases!
7 ctx.UpdateObject(p);
8 }
9 ctx.SaveChanges();

就是这样,哈。

警告:

仅当客户端与服务器两端属性名称相同时这才可以工作,因为没有方法重命名属性。

同样由于数据服务客户端中对象实体化工作的方式,当你的类有一个Reference属性及一个backing外键属性并且类进行了自动fix-up使两个值保持一致时这将不会工作。

提示53. 怎样调试EF POCO映射的问题

如果你尝试在EF4.0中使用POCO类,相对更容易在将模型映射到CLR类时遇到问题。

如果你在这里遇到任何恼人的问题,弄清楚事情的最佳的方法是尝试显式为POCO类型加载元数据。

例如,假如Product是一个POCO类型,并且你在使其工作时遇到问题,你可以尝试下这段代码来找出问题是什么:

1 using (MyContext ctx = new MyContext())
2 {
3 ctx.MetadataWorkspace.LoadFromAssembly(
4 typeof(Product).Assembly,
5 MappingWarning
6 );
7 }

其中MappingWarning可能是任意接受一个字符串且无返回值的方法,如这样:

1 public void MappingWarning(string warning)
2 {
3 Console.WriteLine(warning);
4 }

当你完成这步,EF将遍历你程序集中的类型,试图查看它们是否与概念模型中的类型匹配,如果某些原因一个CLR类型被识别并且随后被排除-不是一个有效的匹配-你的方法将被调用,接着在我们例子中警告会弹出到控制台。

提示54. 怎样使用声明表达式(Statement Expression)提升性能

背景:

在最近编写数据服务提供程序系列博文的过程中,我停止编写这种使用反射将属性值由一个对象拷贝到另一个的代码段:

 1 foreach (var prop in resourceType
2 .Properties
3 .Where(p => (p.Kind & ResourcePropertyKind.Key)
4 != ResourcePropertyKind.Key))
5 {
6 var clrProp = clrType
7 .GetProperties()
8 .Single(p => p.Name == prop.Name);
9 var defaultPropValue = clrProp
10 .GetGetMethod()
11 .Invoke(resetTemplate, new object[] { });
12 clrProp
13 .GetSetMethod()
14 .Invoke(resource, new object[] { defaultPropValue });
15 }

问题:

这段代码至少有两个主要问题。

  1. 其通过反射在一个循环中查找属性与方法
  2. 在一个循环中使用反射调用那些方法。

我们可以将所有属性的get/set方法存储于一些可具有缓存能力的数据结构来解决问题(1)。

但是修复问题(2)需要多一点技巧,要使这段代码真正泛型化,你需要一些如轻量级代码生成之类的东西。

解决方案:

多亏.NET4.0添加了声明表达式。这意味着现在你可以创建描述多行声明的表达式,并且那些语句可以像其他任务一样执行。

波拉特说‘好’…

在网上一番搜索,找到了Bart的这篇关于声明表达式的优秀博文,这足以让我激动万分,希望对你也是如此。

步骤1 – 熟悉API

带着发现新东西的热情,我决定先找些简单的东西尝试,尝试把这个委托对象转换为一个表达式:

1 Func<int> func = () => {
2 int n;
3 n=2;
4 return n;
5 };

如果你只这样做,结果很可怕:

1 Expression<Func<int>> expr = () => {
2 int n;
3 n = 2;
4 return n;
5 };

但不幸的是当前C#不支持这种写法,取而代之的是你需要手工构建这个表达式,像这样:

 1 var n = Expression.Variable(typeof(int));
2 var expr = Expression.Lambda<Func<int>>
3 (
4 Expression.Block(
5 // int n;
6 new[] { n },
7 // n = 2;
8 Expression.Assign(
9 n,
10 Expression.Constant(2)
11 ),
12 // return n;
13 n
14 )
15 );

相当容易哈。

步骤2 – 证明我们可以指定到一个属性

现在我们已经体验了下这个API,到时间用它来解决我们的问题了。

我们需要的是一个函数,其通过重设一个对象的一个或多个属性来修改一个对象(本例中为product),像这样:

1 Action<Product> baseReset = (Product p) => { p.Name = null; };

这段代码使用新的表达式API来创建一个等价的操作(action):

 1 var parameter = Expression.Parameter(typeof(Product));
2 var resetExpr = Expression.Lambda<Action<Product>>(
3 Expression.Block(
4 Expression.Assign(
5 Expression.Property(parameter,"Name"),
6 Expression.Constant(null, typeof(string))
7 )
8 ),
9 parameter
10 );
11 var reset = resetExpr.Compile();
12 var product = new Product { ID = 1, Name = "Foo" };
13 reset(product);

果然当重置(product)被调用后,product的Name变为null。

步骤3 – 设定到所有的非键属性

现在我们需要做的只是创建一个函数,其接收到一个特定的CLR类型时将创建一个表达式来重置所有的非键属性。

实际上收集属性与它们期望的值的列表,对于本话题不是很重要,所以让我们想象我们已经将获得的信息放到一个dictionary中,如这样:

1 var properties = new Dictionary<PropertyInfo, object>();
2 var productProperties = typeof(Product).GetProperties();
3 var nameProp = productProperties.Single(p => p.Name == "Name");
4 var costProp = productProperties.Single(p => p.Name == "Cost");
5 properties.Add(nameProp, null);
6 properties.Add(costProp, 0.0M);

有了这个数据结构后,我们的工作就是创建一个与下面的Action有同样效果的表达式:

1 Action<Product> baseReset = (Product p) => {
2 p.Name = null;
3 p.Cost = 0.0M;
4 };

首先我需要创建所有的赋值表达式:

 1 var parameter = Expression.Parameter(typeof(Product));
2 List<Expression> assignments = new List<Expression>();
3 foreach (var property in properties.Keys)
4 {
5 assignments.Add(Expression.Assign(
6 Expression.Property(parameter, property.Name),
7 Expression.Convert(
8 Expression.Constant(
9 properties[property],
10 property.PropertyType
11 )
12 )
13 );
14 }

接下来我们把赋值表达式注入Lambda内一个block中,编译整个项目并测试我们的新函数:

 1 var resetExpr = Expression.Lambda<Action<Product>>(
2 Expression.Block(
3 assignments.ToArray()
4 ),
5 parameter
6 );
7 var reset = resetExpr.Compile();
8 var product = new Product { ID = 1, Name = "Foo", Cost = 34.5M };
9 reset(product);
10 Debug.Assert(product.Name == null);
11 Debug.Assert(product.Cost == 0.0M);

正如期待的这很好用。

要解决这个问题,我准备放在更新的博文中,我们需要一个以类型为键dictionary,我们可以用其来存储一个特定类型重置动作。这样如果一个类型的重置动作没有找到我们只需创建一个新的…

并且性能问题应该已经成为过去

提示55. 怎样通过包装来扩展一个IQueryable

在过去几年中,我在很多情况下都想要深入底层看看IQueryable内部到底发生什么,但我一直没有找到一个简单的方法,至少到目前为止。

像这样深入并做一些东西很有趣,这样你可以:

  • 在查询执行前进行Log
  • 重写表达式,例如替换一个提供程序-如EF, LINQ to SQL, LINQ to Objects等不支持的表达式为可以被支持的。

无论如何,我很高兴不久之前当查看Vitek(BTW,他的新博客在这)完成的一些示例代码,我意识到我可以将其一般化来创建一个InterceptedQuery<>与一个InterceptingProvider。

基本思想是使你可以这样使用IQueryable:

1 public IQueryable<Customer> Customers
2 {
3 get{
4 return InterceptingProvider.CreateQuery(_ctx.Customers, visitor);
5 }
6 }

此处visitor是一个会被访问的ExpressionVisitor,在将查询提交到底层(本例中就是Entity Frameork)之前,其可能会重写任何Customers组成的查询。

实现

多亏Vitek才使这个实现变得相当简单。

让我们开始实现InterceptedQuery<>,实际上这不值一提:

 1 public class InterceptedQuery<T> : IOrderedQueryable<T>
2 {
3 private Expression _expression;
4 private InterceptingProvider _provider;
5
6 public InterceptedQuery(
7 InterceptingProvider provider,
8 Expression expression)
9 {
10 this._provider = provider;
11 this._expression = expression;
12 }
13 public IEnumerator<T> GetEnumerator()
14 {
15 return this._provider.ExecuteQuery<T>(this._expression);
16 }
17 IEnumerator IEnumerable.GetEnumerator()
18 {
19 return this._provider.ExecuteQuery<T>(this._expression);
20 }
21 public Type ElementType
22 {
23 get { return typeof(T); }
24 }
25 public Expression Expression
26 {
27 get { return this._expression; }
28 }
29 public IQueryProvider Provider
30 {
31 get { return this._provider; }
32 }
33 }

接下来是InterceptedProvider,这更有趣:

 1 public class InterceptingProvider : IQueryProvider
2 {
3 private IQueryProvider _underlyingProvider;
4 private Func<Expression,Expression>[] _visitors;
5
6 private InterceptingProvider(
7 IQueryProvider underlyingQueryProvider,
8 params Func<Expression,Expression>[] visitors)
9 {
10 this._underlyingProvider = underlyingQueryProvider;
11 this._visitors = visitors;
12 }
13
14 public static IQueryable<T> Intercept<T>(
15 IQueryable<T> underlyingQuery,
16 params ExpressionVisitor[] visitors)
17 {
18 Func<Expression, Expression>[] visitFuncs =
19 visitors
20 .Select(v => (Func<Expression, Expression>) v.Visit)
21 .ToArray();
22 return Intercept<T>(underlyingQuery, visitFuncs);
23 }
24
25 public static IQueryable<T> Intercept<T>(
26 IQueryable<T> underlyingQuery,
27 params Func<Expression,Expression>[] visitors)
28 {
29 InterceptingProvider provider = new InterceptingProvider(
30 underlyingQuery.Provider,
31 visitors
32 );
33 return provider.CreateQuery<T>(
34 underlyingQuery.Expression);
35 }
36 public IEnumerator<TElement> ExecuteQuery<TElement>(
37 Expression expression)
38 {
39 return _underlyingProvider.CreateQuery<TElement>(
40 InterceptExpr(expression)
41 ).GetEnumerator();
42 }
43 public IQueryable<TElement> CreateQuery<TElement>(
44 Expression expression)
45 {
46 return new InterceptedQuery<TElement>(this, expression);
47 }
48 public IQueryable CreateQuery(Expression expression)
49 {
50 Type et = TypeHelper.FindIEnumerable(expression.Type);
51 Type qt = typeof(InterceptedQuery<>).MakeGenericType(et);
52 object[] args = new object[] { this, expression };
53
54 ConstructorInfo ci = qt.GetConstructor(
55 BindingFlags.NonPublic | BindingFlags.Instance,
56 null,
57 new Type[] {
58 typeof(InterceptingProvider),
59 typeof(Expression)
60 },
61 null);
62
63 return (IQueryable)ci.Invoke(args);
64 }
65 public TResult Execute<TResult>(Expression expression)
66 {
67 return this._underlyingProvider.Execute<TResult>(
68 InterceptExpr(expression)
69 );
70 }
71 public object Execute(Expression expression)
72 {
73 return this._underlyingProvider.Execute(
74 InterceptExpr(expression)
75 );
76 }
77 private Expression InterceptExpr(Expression expression)
78 {
79 Expression exp = expression;
80 foreach (var visitor in _visitors)
81 exp = visitor(exp);
82 return exp;
83 }
84 }

注意任何时候查询被执行,我们拦截当前表达式,其中依次调用了所有已注册的'visitors',然后在底层提供程序上执行最终的表达式。

实现说明:

在基础架构中我们的实现使用了 Func<Expression,Expression> 而非.NET4.0的System.Linq.Expressions.ExpressionVisitor,主要因为.NET对于社区来的稍显迟了, 所以当前很多visitor没有继承自System.Linq.Expressions.ExpressionVisitor。

你只需要再看一下Matt Warren的极佳的IQToolkit,里面有很多例子。

然而我们想鼓励使用System.Linq.Expressions.ExpressionVisitor,所以对此也有一个方便的重载。

同时记住如果你包装了Entity Framework,并进行了任何重写的比较复杂的查询,你需要避免任何调用表达式 – 见Colin的博文

IQToolbox比较有用的visitor之一被称作ExpressionWriter及其带的一些小插件-其公开了所有构造函数与基础Visit方法-你可以使用它在Entity Framework查询被执行前将表达式输出到控制台:

1 CustomersContext _ctx = new CustomersContext();
2 ExpressionWriter _writer = new ExpressionWriter(Console.Out);
3 public IQueryable<Customer> Customers{
4 get{
5 return InterceptingProvider.Intercept(_ctx.Customers, _writer.Visit);
6 }
7 }

同样你需要留心我们在CreateQuery这个非强类型的方法中使用IQToolbox中有用的TypeHelper类,它确实帮助我们创建正确的泛型InterceptedQuery<>类型的示例。

再次谢谢Matt!

把这些合在一起:

展示最后一个例子。

这里我模拟WCF/ADO.NET数据服务的工作来处理这个请求:

GET ~/People/?$filter=Surname eq 'James'

如果后端是一个非强类型的数据服务提供程序。

 1 // Create some data
2 List<Dictionary<string, object>> data = new List<Dictionary<string, object>>();
3 data.Add(
4 new Dictionary<string, object>{{"Surname", "James"}, {"Firstname", "Alex"}}
5 );
6 data.Add(
7 new Dictionary<string, object>{{"Surname", "Guard"}, {"Firstname", "Damien"}}
8 );
9 data.Add(
10 new Dictionary<string, object>{{"Surname", "Meek"}, {"Firstname", "Colin"}}
11 );
12 data.Add(
13 new Dictionary<string, object>{{"Surname", "Karas"}, {"Firstname", "Vitek"}}
14 );
15 data.Add(
16 new Dictionary<string, object>{{"Surname", "Warren"}, {"Firstname", "Matt"}}
17 );
18 // Create a couple of visitors
19 var writer = new ExpressionWriter(Console.Out);
20 var dspVisitor = new DSPExpressionVisitor();
21
22 // Intercept queries to the L2O IQueryable
23 var queryRoot = InterceptingProvider.Intercept(
24 data.AsQueryable(), // L2O’s iqueryable
25 writer.Visit, // What does the expression look like first?
26 dspVisitor.Visit, // Replace GetValue().
27 writer.Visit // What does the expression look like now?
28 );
29
30 // Create a Data Services handle for the Surname property
31 ResourceProperty surname = new ResourceProperty(
32 "Surname",
33 ResourcePropertyKind.Primitive,
34 ResourceType.GetPrimitiveResourceType(typeof(string))
35 );
36
37 // Create a query without knowing how to access the Surname
38 // from x.
39 var query =
40 from x in queryRoot
41 where ((string) DataServiceProviderMethods.GetValue(x, surname))
42 == "James"
43 select x;
44
45 // Execute the query and print some results
46 foreach (var x in query)
47 Console.WriteLine("Found Match:{0}",
48 x["Firstname"].ToString()
49 );

如所见在一个字典列表中我们有一些People数据,我们试图找到姓氏为'James'的那个人。

问题是数据服务不知道怎样有一个字典中得到姓氏。所以它注入一个请求到 DataServiceProviderMethods.GetValue(..) 。

好。

不幸的是这时LINQ to Objects查询提供程序没有足够的上下文信息来处理这个查询 – 像我们在这个查询中这样盲目的调用GetValue会失败。

所以我们拦截这个查询,DSPExpressionVisitor(此处我将不深入)简单的将

DataServiceProviderMethods.GetValue(x, surname)

替换为这样:

x[surname.Name]

如果你查找形式,你可以看到这与下面的是相同的:

x["Surname"]

所以当整个表达式被访问时,你最终会得到如下这样的查询:

1 var query =
2 from x in queryRoot
3 where ((string) x[surname.Name]) == "James"
4 select x;

对于这个Linq to Objects可以很好的处理!

摘要

这是一个通用目的的解决方案允许将一个IQueryable叠加在另一个上,并在查询表达式被传入底层提供程序前翻译/重写/记录它。

Enjoy。

Entity Framework技巧系列之十三 - Tip 51 - 55的更多相关文章

  1. (翻译)Entity Framework技巧系列之十 - Tip 37 - 41

    提示37. 怎样进行按条件包含(Conditional Include) 问题 几天前有人在StackOverflow上询问怎样进行按条件包含. 他们打算查询一些实体(比方说Movies),并且希望预 ...

  2. Entity Framework技巧系列之十一 - Tip 42 - 45

    提示42. 怎样使用Code-Only创建一个动态模型 背景: 当我们给出使用Code-Only的例子,总是由创建一个继承自ObjectContext的强类型的Context开始.这个类用于引导模型. ...

  3. Entity Framework技巧系列之十 - Tip 37 - 41

    提示37. 怎样进行按条件包含(Conditional Include) 问题 几天前有人在StackOverflow上询问怎样进行按条件包含. 他们打算查询一些实体(比方说Movies),并且希望预 ...

  4. Entity Framework技巧系列之二 - Tip 6 - 8

    提示6. 如何及何时使用贪婪加载 什么时候你需要使用贪婪加载? 通常在你的程序中你知道对查询到的实体将要进行怎样的操作. 例如,如果你查询一个订单以便为一个客户重新打印,你知道没有组成订单的项目即产品 ...

  5. Entity Framework技巧系列之九 - Tip 35 - 36

    提示35. 怎样实现OfTypeOnly<TEntity>()这样的写法 如果你编写这样LINQ to Entities查询: 1 var results = from c in ctx. ...

  6. Entity Framework技巧系列之五 - Tip 16 – 19

    提示16. 当前如何模拟.NET 4.0的ObjectSet<T> 背景: 当前要成为一名EF的高级用户,你确实需要熟悉EntitySet.例如,你需要理解EntitySet以便使用 At ...

  7. Entity Framework技巧系列之六 - Tip 20 – 25

    提示20. 怎样处理固定长度的主键 这是正在进行中的Entity Framework提示系列的第20篇. 固定长度字段填充: 如果你的数据库中有一个固定长度的列,例如像NCHAR(10)类型的列,当你 ...

  8. Entity Framework技巧系列之十二 - Tip 46 - 50

    提示46. 怎样使用Code-Only排除一个属性  这次是一个真正简单的问题,由StackOverflow上这个问题引出.  问题:  当我们使用Code-Only把一个类的信息告诉Entity F ...

  9. Entity Framework技巧系列之十四 - Tip 56

    提示56. 使用反射提供程序编写一个OData Service 在TechEd我收到一大堆有关将数据作为OData暴露的问题. 到目前为止你大概知道可以使用数据服务与Entity Framework将 ...

随机推荐

  1. 使用PowerDesigner创建mysql数据库表图

    使用PowerDesigner 建数据库表. 一直很忙,没有时间写东西.这次搞点会声会色的,嘿嘿 此技能为项目经理必备技能. 本次主角: 1.在workspace下建立一项目: physical da ...

  2. 老司机的奇怪noip模拟T2-huangyueying

    2. 黄月英(huangyueying.cpp/c/pas )[问题描述]xpp 每天研究天文学研究哲学,对于人生又有一些我们完全无法理解的思考.在某天无聊学术之后, xpp 打开了 http://w ...

  3. 第二节windows系统下Xshell 5软件远程访问虚拟机 Linux系统

    下载Xshell 5软件在windows下安装 安装好后Xshell 5启动软件 下一步,检查虚拟机,配置是否正确 下一步,设置网络,保障虚拟机系统能够连接网络 下一步,进入虚拟机系统,检查虚拟机网络 ...

  4. [妙味DOM]第五课:事件深入应用

    知识点总结 鼠标拖拽原理: 1.鼠标按下后开始移动,鼠标抬起停止移动,即onmousedown中要包括onmousemove和onmouseup 2.获取位置的计算:获取鼠标的当前位置-鼠标在物体中的 ...

  5. Java中自己实现枚举

    public class MyEnum { private final String name; public  static final MyEnum red = new MyEnum(" ...

  6. IOS 类的属性修饰符atomic

    在声明一个类的属性时,默认这个属性会被修饰atomic,意思是原子性访问的. nonatomic和atomic修饰的属性,在自己没有重写setter和getter的时候才会发生作用,其主要的作用可以理 ...

  7. iptables允许FTP

    在iptables里设置允许访问ftp(建立连接,数据传输) 由于ftp服务在建立连接和传输数据时,使用的时不同的端口,建立连接的端口20.21,数据传输的端口可以自定义: 修改ftp配置文件,指定用 ...

  8. 再谈Java方法传参那些事

    把一个变量带进一个方法,该方法执行结束后,它的值有时会改变,有时不会改变.一开始会觉得--“好神奇呀”.当我们了解java内存分析的知识后,一切都是那么简单明了了--“哦,这么回事呀”.但是今天的上机 ...

  9. Kettle jdbc连接hive出现问题

    jdbc连接时报如下错误: Error connecting to database [k] : org.pentaho.di.core.exception.KettleDatabaseExcepti ...

  10. 关于jquery选择器中:first和:first-child和:first-of-type的区别及:nth-child()和:nth-of-type()的区别

    :first:选择第一个出现符合的元素 :first-child:选择限制条件中的第一个元素,并且必须和冒号前面的标签一致 :first-of-type:选择所有限制条件下的第一个冒号前面的标签元素, ...