# .NET开发向导

# 1.SQL API

C#/.NET开发者可以使用特定的SQL API来查询和修改数据库中的数据。

# 1.1.SqlFieldsQueries

SqlFieldsQuery接受一个标准SQL作为其构造器的参数,下面是其执行查询的代码。可以只选定特定的字段,来最小化网络和序列化开销。

    可查询字段定义

    在特定字段可以被SqlFieldsQuery访问之前,它们应做为SQL模式的一部分,使用标准的DDL命令,或者特定的.NET属性,或者QueryEntity配置,都可以进行字段的定义。

    通过SqlFieldsQuery,可以执行后续的DML命令来对数据进行修改:

      # 1.2.示例

      Ignite的二进制包中包含了可以直接运行的QueryDmlExample源代码在这里,该示例演示了上述所有DML操作的用法。

      # 1.3.SQL查询问题解决

      当SQL查询失败时(查询解析失败或者其他的异常),要看一下InnerException属性,它带有从IgniteSQL引擎抛出来的所有错误信息,还有失败的确切原因等详细信息,具体可以看Visual Studio的调试器或者调用异常对象的ToString()方法。

      try
      {
          IQueryCursor<List> cursor = cache.QueryFields(query);
      }
      catch (IgniteException e)
      {
          Console.WriteLine(e.ToString());
      }
      

      # 2.模式和索引

      除了常规的DDL命令,C#/.NET开发者还可以使用特定的SQL API来进行模式和索引的定义。

      # 2.1.基于属性的配置

      可以通过使用QuerySqlFieldAttributeQueryTextFieldAttribute标记可缓存类型成员来配置索引。这些类型应传递给CacheConfiguration(string name, params Type[] queryTypes)的构造函数或QueryEntity的构造函数。

      var cfg = new IgniteConfiguration
      {
          CacheConfiguration = new[]
          {
            	// Configure queries for a cache with Person values (cache keys are not indexed)
              new CacheConfiguration("personCache", typeof(Person)),
            	new CacheConfiguration
              {
                  QueryEntities =
                  {
                  		// Configure indexing for both keys and values
                      new QueryEntity(typeof(int), typeof(Company))
                  }
              }
          }
      };
      

      将字段或属性标记为对SQL查询可见

      要使字段或者属性对于SQL查询可见,需要将它们用[QuerySqlField]进行标记,Age在SQL中是无法访问的,注意这些属性都是没有索引的。

      public class Employee
      {
          [QuerySqlField]
          public string Name { get; set; }
      
          [QuerySqlField]
          public long Salary { get; set; }
      
          public int Age { get; set; }
      }
      

      嵌套对象索引

      嵌套对象的属性也可以索引和查询,比如下面的Person对象,其有一个Address对象作为属性:

      public class Person
      {
        /** Indexed field. Will be visible for SQL engine. */
        [QuerySqlField(IsIndexed = true)]
        private long _id;
      
        /** Queryable field. Will be visible for SQL engine. */
        [QuerySqlField]
        private string _name;
      
        /** Will NOT be visible for SQL engine. */
        private int _age;
      
        /** Indexed field. Will be visible for SQL engine. */
        [QuerySqlField(IsIndexed = true)]
        private Address _address;
      }
      

      Address类的结构如下:

      public class Address
      {
        /** Indexed field. Will be visible for SQL engine. */
        [QuerySqlField(IsIndexed = true)]
        private string _street;
      
        /** Indexed field. Will be visible for SQL engine. */
        [QuerySqlField(IsIndexed = true)]
        private int _zip;
      }
      

      上面的代码中,Address类中的所有属性都指定了[QuerySqlField(IsIndexed = true)],和Person类中的Address对象一样。

      可以执行的查询如下:

      var cursor = personCache.QueryFields(new SqlFieldsQuery(
        "select * from Person where street = 'street1'"));
      

      注意,不要在SQL查询的WHERE子句中指定address.street,这是因为Address类的字段会在Person表内展开,这样只需要在查询中简单地直接使用Address中的字段就可以了。

      字段名和序列化

      带有[QuerySqlField]标记的字段或属性会自动添加QueryField到相应的QueryEntity,因此要确保SQL字段名和序列化的字段名相同。

      例如,默认的Ignite反射式序列化器对字段进行操作,如果遵循标准命名约定,则辅助字段名将为_name,并且该名称用于序列化。因此必须使用[QuerySqlField]标记该字段而非属性,然后在SQL查询中使用_name

      不过对于自动属性,它会起作用,因为Ignite可以识别它们并修剪辅助字段说明符。

      预定义字段

      除了标有[QuerySqlField]属性的所有字段外,每个表还有两个特殊的预定义字段_key_val,它们表示到整个键和值对象的链接。这很有用,例如,当其中一个是基本类型并且希望按其值进行过滤时,类似的查询为:SELECT * FROM Person WHERE _key = 100

      单列索引

      如果不仅仅要让字段可以通过SQL访问,还希望加速查询,那就需要索引该字段的值。如果要创建一个单列索引,需要在该字段上加上[QuerySqlField(IsIndexed = true)]属性。

      public class Employee
      {
          // Index in ascending order
          [QuerySqlField(IsIndexed = true)]
          public string Name { get; set; }
      
          // Index in descending order
          [QuerySqlField(IsIndexed = true, IsDescending = true)]
          public long Salary { get; set; }
      
          // Enable field in SQL, but don't index
          [QuerySqlField]
          public int Age { get; set; }
      }
      

      组合索引

      通过多字段索引可以加速复杂条件的查询,做法是使用QuerySqlField.IndexGroups属性。如果希望一个字段参与多个组合索引,也可以将多个组加入IndexGroups数组。

      比如下面的组合索引示例,有个名为Age的属性,它参与了名为age_salary_idx的倒序组合索引,在同一个组合索引中,还有一个正序排列的salary属性,而在salary字段之上,它本身还建了弹列索引。

      public class Employee
      {
          [QuerySqlField(IsIndexed = true, IndexGroups = new[] {"age_salary_idx"}, IsDescending = true)]
          public int Age { get; set; }
      
          [QuerySqlField(IsIndexed = true, IndexGroups = new[] {"age_salary_idx", "salary_idx"})]
          public long Salary { get; set; }
      }
      

      运行时修改索引和可查询字段

      如果需要在运行时管理索引或者使新的字段对SQL引擎可见,可以使用ALTER TABLE, CREATE/DROP INDEX命令。

      # 2.2.基于QueryEntity的配置

      索引和字段也可以通过Apache.Ignite.Core.Cache.Configuration.QueryEntity进行配置,通过代码、app.config和Spring配置文件都可以。这个和上面的基于属性的配置是等价的,因为在内部属性最后也会被转换为QueryEntity。

        运行时修改索引和可查询字段

        如果需要在运行时管理索引或者使新的字段对SQL引擎可见,可以使用ALTER TABLE, CREATE/DROP INDEX命令。

        注意

        确保在app.config中要使用程序集限定类型名。

        Java类型名映射

        因为SQL查询是在Java中通过H2引擎执行的,Ignite.NET通过QueryEntityQueryField中的成对的属性,将.NET类型映射到Java类型。

        • QueryEntity.KeyTypeQueryEntity.KeyTypeName
        • QueryEntity.ValueTypeQueryEntity.ValueTypeName
        • QueryField.FieldTypeQueryField.FieldTypeName

        Type属性是.NET类型,TypeName属性是Java类型名,Type属性自动配置TypeName属性,但是反过来不行。

        • 自定义类型通过简称进行映射(不带命名空间的类型名);
        • 基本类型、String和Guids会映射到对应的Java类型;
        • DateTime会被映射到Timestamp,查询中使用的所有DateTime值必须为UTC时间;
        • sbyteushortuintulong在Java中不存在,会被使用逐位转换(对于超出范围的值,SQL可能无法按预期方式执行)映射到byteshortintlong

        DateTime和SQL

        DateTime可以是本地时间和UTC时间,而Java的Timestamp只能是UTC时间。因此,Ignite.NET可以通过两种方式序列化DateTime:.NET风格(可以与非UTC值一起使用,在SQL中不起作用)和Timestamp风格(向非UTC值抛出异常,在SQL中可以正常工作)。

        反射式序列化:用[QuerySqlField]标记字段以强制Timestamp序列化。

        IBinarizable:使用IBinaryWriter.WriteTimestamp方法。

        当无法给字段标记[QuerySqlField]或实现IBinarizable时,可以使用IBinarySerializer方法,具体请参见序列化

        ::: SQL Date函数 日期和时间SQL函数(例如HOUR)会根据当前时区生成结果,鉴于其他所有条件均为UTC,这可能会出现意外结果。要为SQL函数强制使用UTC,请使用-Duser.timezone=UTCJVM选项(通过IgniteConfiguration.JvmOptions)。 :::

        # 2.3.自定义主键

        如果主键只使用了预定义的SQL数据类型,则无需使用与模式相关的配置来执行其他操作。

        预定义SQL数据类型

        • 所有的基本类型(包括可为空的形式),除了char
        • string
        • decimal
        • byte[]
        • DateTime
        • Guid

        不过一旦决定引入自定义复杂主键并在DML语句中引用其字段,就必须在QueryEntity配置中将QueryField.IsKeyField设置为true。使用基于属性的配置时,不需要任何额外的步骤,QueryEntity.KeyType中所有标有[QuerySqlField]的字段将被视为主键字段。当使用手动QueryEntity配置时,IsKeyField应显式配置。

          ::: 自动GetHashCode和Equals实现 如果对象可以序列化为二进制形式,则Ignite将在序列化期间计算其哈希值并将其写入最后的二进制数组。此外,Ignite还提供了Equals方法的自定义实现,以满足二进制对象的比较需求。这意味着无需覆盖自定义键和值的GetHashCodeEquals方法即可在Ignite中使用它们,具体请参见序列化。 :::

          # 3.LINQ

          Apache Ignite.NET面向缓存SQL查询为LINQ提供了支持,这样就可以避免处理SQL语法,直接在C#中编写查询。Ignite的LINQ实现支持用于缓存查询的ANSI-99 SQL的所有功能:分布式关联、分组、聚合、字段查询等。

          # 3.1.安装

          二进制包:添加对Apache.Ignite.Linq.dll的引用。

          NuGetInstall-Package Apache.Ignite.Linq

          # 3.2.配置

          SQL索引的配置方式应与正常的SQL查询一样,具体请参见DDL模式和索引的相关章节。

          # 3.3.用法

          Apache.Ignite.Linq.CacheLinqExtensions类是LINQ实现的入口。

          通过调用缓存的AsCacheQueryable方法获取IQueryable实例,在其上即可使用LINQ:

          ICache<EmployeeKey, Employee> employeeCache = ignite.GetCache<EmployeeKey, Employee>(CacheName);
          
          IQueryable<ICacheEntry<EmployeeKey, Employee>> queryable = cache.AsCacheQueryable();
          
          Employee[] interns = queryable.Where(emp => emp.Value.IsIntern).ToArray();
          

          注意

          可以直接在缓存实例上使用LINQ,无需调用AsCacheQueryable()。不过这将导致LINQ的Objects查询在本地获取并处理整个缓存数据集,这是非常低效的。

          # 3.4.内省

          Ignite的LINQ实现在底层使用ICache.QueryFields。在具体化语句(ToList、ToArray等)之前,可以在任何时点通过将IQueryable强制转换为ICacheQueryable来检查生成的SqlFieldsQuery

          // Create query
          var query = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable().Where(emp => emp.Value.IsIntern);
          
          // Cast to ICacheQueryable
          var cacheQueryable = (ICacheQueryable) query;
          
          // Get resulting fields query
          SqlFieldsQuery fieldsQuery = cacheQueryable.GetFieldsQuery();
          
          // Examine generated SQL
          Console.WriteLine(fieldsQuery.Sql);
          
          // Output: select _T0._key, _T0._val from "persons".Person as _T0 where _T0.IsIntern
          

          # 3.5.投影

          ICacheEntry上可以进行简单的WHERE查询操作,可以选择键、值、或者单独的任何键和值,通过匿名类型也可以选择多个字段。

          var query = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable().Where(emp => emp.Value.IsIntern);
          
          IQueryable<EmployeeKey> keys = query.Select(emp => emp.Key);
          
          IQueryable<Employee> values = query.Select(emp => emp.Value);
          
          IQueryable<string> names = values.Select(emp => emp.Name);
          
          var custom = query.Select(emp => new {Id = emp.Key, Name = emp.Value.Name, Age = emp.Value.Age});
          

          # 3.6.编译查询

          LINQ实现会因表达式解析和SQL生成而导致某些开销,因此可能要为频繁访问的查询消除这个开销。

          Apache.Ignite.Linq.CompiledQuery类提供了查询的编译以便重用,调用其Compile方法可以创建一个新的表示已编译的查询的委托,所有查询参数都应在该委托的参数中。

          var queryable = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable();
          
          // Regular query
          var persons = queryable.Where(emp => emp.Value.Age > 21);
          var result = persons.ToArray();
          
          // Corresponding compiled query
          var compiledQuery = CompiledQuery.Compile((int age) => queryable.Where(emp => emp.Value.Age > age));
          IQueryCursor<ICacheEntry<EmployeeKey, Employee>> cursor = compiledQuery(21);
          result = cursor.ToArray();
          

          # 3.7.关联

          相同缓存和跨缓存的关联都是可能的,当同一缓存中有多种类型的数据时,将使用相同缓存关联,因此建议为每种数据类型创建单独的缓存。

          var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable();
          var orgs = ignite.GetCache<int, Organization>("orgCache").AsCacheQueryable();
          
          // SQL join on Person and Organization to find persons working for Apache
          var qry = from person in persons
            				from org in orgs
            				where person.Value.OrgId == org.Value.Id && org.Value.Name == "Apache"
            				select person
          
          foreach (var cacheEntry in qry)
              Console.WriteLine(cacheEntry.Value);
          
          // Same query with method syntax
          qry = persons.Join(orgs, person => person.Value.OrgId, org => org.Value.Id, (person, org) => new {person, org}).Where(p => p.org.Name == "Apache").Select(p => p.person);
          

          # 3.8.Contains

          ICollection.Contains是支持的,当希望通过一组id获取数据时这个很有用,比如:

          var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable();
          var ids = new int[] {1, 20, 56};
          
          var personsByIds = persons.Where(p => ids.Contains(p.Value.Id));
          

          这个会翻译成... where Id IN (?, ?, ?)

          不过IN不使用索引(见性能和调试),这个还不能使用编译查询,因为参数数量是可变的,更好的做法是在ids集合上使用Join

          var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable();
          var ids = new int[] {1, 20, 56};
          
          var personsByIds = persons.Join(ids,
                                          person => person.Value.Id,
                                          id => id,
                                          (person, id) => person);
          

          这会翻译成一个临时的表关联:select _T0._KEY, _T0._VAL from "person".Person as _T0 inner join table (F0 int = ?) _T1 on (_T1.F0 = _T0.ID),这会只有一个数组参数,因此计划会被正确地缓存,编译查询也可以使用了。

          # 3.9.支持的SQL函数

          以下是Ignite LINQ实现支持的.NET函数及其对应的SQL函数列表:

          .NET SQL
          String.Length LENGTH
          String.ToLower LOWER
          String.ToUpper UPPER
          String.StartsWith("foo") LIKE 'foo%'
          String.EndsWith("foo") LIKE '%foo'
          String.Contains("foo") LIKE '%foo%'
          String.IndexOf("abc") INSTR(MyField, 'abc') - 1
          String.IndexOf("abc", 3) INSTR(MyField, 'abc', 3) - 1
          String.Substring("abc", 4) SUBSTRING(MyField, 4 + 1)
          String.Substring("abc", 4, 7) SUBSTRING(MyField, 4 + 1, 7)
          String.Trim() TRIM
          String.TrimStart() LTRIM
          String.TrimEnd() RTRIM
          String.Trim('x') TRIM(MyField, 'x')
          String.TrimStart('x') LTRIM(MyField, 'x')
          String.TrimEnd('x') RTRIM(MyField, 'x')
          String.Replace REPLACE
          String.PadLeft LPAD
          String.PadRight RPAD
          Regex.Replace REGEXP_REPLACE
          Regex.IsMatch REGEXP_LIKE
          Math.Abs ABS
          Math.Acos ACOS
          Math.Asin ASIN
          Math.Atan ATAN
          Math.Atan2 ATAN2
          Math.Ceiling CEILING
          Math.Cos COS
          Math.Cosh COSH
          Math.Exp EXP
          Math.Floor FLOOR
          Math.Log LOG
          Math.Log10 LOG10
          Math.Pow POWER
          Math.Round ROUND
          Math.Sign SIGN
          Math.Sin SIN
          Math.Sinh SINH
          Math.Sqrt SQRT
          Math.Tan TAN
          Math.Tanh TANH
          Math.Truncate TRUNCATE
          DateTime.Year YEAR
          DateTime.Month MONTH
          DateTime.Day DAY_OF_MONTH
          DateTime.DayOfYear DAY_OF_YEAR
          DateTime.DayOfWeek DAY_OF_WEEK - 1
          DateTime.Hour HOUR
          DateTime.Minute MINUTE
          DateTime.Second SECOND

          18624049226

          最后更新时间:: 10/21/2020, 4:44:25 PM