0%

复制代码

using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; /**
*
* jackchain
* QQ:710782046
* 2017-08-31
* 动态构造OrderBy Linq序列
* */

namespace UFX.Tools
{ public class ConvertExtOrderLinq
{ ///


/// 动态转换为Linq排序 ///

///
/// [aaa,bbb,ccc],[asc,asc,desc]
///
public static Func<IQueryable, IOrderedQueryable> GetOrderBy(List<string> orderColumn, List<string> orderDir)
{ string ascKey = “OrderBy”; string descKey = “OrderByDescending”;

        Type typeQueryable \= typeof(IQueryable<T>);
        ParameterExpression argQueryable \= Expression.Parameter(typeQueryable, "jk"); var outerExpression = Expression.Lambda(argQueryable, argQueryable); for (int i = 0; i < orderColumn.Count; i++)
        { string columnName = orderColumn\[i\]; string dirKey = orderDir\[i\].ToLower();

            IQueryable<T> query = new List<T>().AsQueryable<T>();
            Type type \= typeof(T);
            ParameterExpression arg \= Expression.Parameter(type, "uf");
            Expression expr \= arg; if (columnName.Contains("."))
            { // support to be sorted on child fields. 
                String\[\] childProperties = columnName.Split('.');
                System.Reflection.PropertyInfo property \= typeof(T).GetProperty(childProperties\[0\]);
                MemberExpression propertyAccess \= Expression.MakeMemberAccess(arg, property); for (int j = 1; j < childProperties.Length; j++)
                {
                    Type t \= property.PropertyType; if (!t.IsGenericType)
                    {
                        property \= t.GetProperty(childProperties\[j\]);
                    } else {
                        property \= t.GetGenericArguments().First().GetProperty(childProperties\[i\]);
                    }
                    type \= property.PropertyType;
                    expr \= Expression.MakeMemberAccess(propertyAccess, property); //propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);

} //property = type.GetProperty(propertyName); //propertyAccess = Expression.MakeMemberAccess(parameter, property);
} else {
PropertyInfo pi = type.GetProperty(columnName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}

            LambdaExpression lambda \= Expression.Lambda(expr, arg); string methodName = dirKey == "asc" ? ascKey : descKey;
            MethodCallExpression resultExp \= Expression.Call(typeof(Queryable), methodName, new Type\[\] { typeof(T), type }, outerExpression.Body, Expression.Quote(lambda));

            outerExpression \= Expression.Lambda(resultExp, argQueryable);

            ascKey \= "ThenBy";
            descKey \= "ThenByDescending";
        } return (Func<IQueryable<T>, IOrderedQueryable<T>>)outerExpression.Compile();
    }

}

}

复制代码

 如何使用?

复制代码

[Action]
[Description(“获取Grid数据”)]
[HttpPost] public JsonResult Get(int start, int length, ExtGridSearch condition, ExtGridMutiSearch[] extCdns, ExtGridOrder[] order)
{ int totalCount = 0;

        Func<IQueryable<DB\_Color>, IOrderedQueryable<DB\_Color>> dyncOrder = ConvertExtOrderLinq.GetOrderBy<DB\_Color>(order.Select(s => s.column).ToList(), order.Select(s => s.dir).ToList()); var list = WMFactory.DBColor.FindByPage(start, length, out totalCount,
                                                  dyncOrder, null, condition, extCdns); return Json(new { data = list, recordsTotal = totalCount, recordsFiltered = totalCount }, JsonRequestBehavior.AllowGet);
    }

复制代码

可结合Datatables 插件一起配合使用

复制代码

//主表格
var DBRangeGrid = $(‘#DBRangeGrid’).dataTable(
{
scrollY: Global_MainTableHeight,
scrollX: true,
autoWidth: true,
scrollCollapse: false, “processing”: true, “serverSide”: true, “ajax”: { “url”: “/Admin/Range/Get”, “type”: “POST”, “data”: function (d) { var dtCols = $(‘#DBRangeGrid’).DataTable().settings().init().columns;
var extOrder = [];
var dtOrders = $(‘#DBRangeGrid’).DataTable().order();
$.each(dtOrders, function (i, item) {
extOrder.push({ column: dtCols[item[0]].data, dir: item[1] });
})
d.order = extOrder;
d.query = $(“#txtSearchKey”).val();
d.fields = [‘IsEnable’, ‘Year.YearName’, ‘Season.SeasonName’, ‘RangeName’, ‘Remark’, ‘CreateUser’, ];
}
}, “ordering”: true, “order”: [[8, “desc”]], “columns”: [
{ “class”: “cbcenter”, “orderable”: false, “title”: ‘‘, “data”: “Id”, “width”: “30px”, “render”: function (data, type, row) { return ‘‘;
}
},
{ “title”: “启用”, “data”: “IsEnable”, render: function (data, tp, row) { if (row.IsEnable == 1) { return “启用“;
} else { return “禁用“;
}
}
},
{ “title”: “年份”, “data”: “Year.YearName” },
{ “title”: “季节”, “data”: “Season.SeasonName” },
{ “title”: “月份”, “data”: “Month” },
{ “title”: “波段名称”, “data”: “RangeName” },
{ “title”: “备注”, “data”: “Remark” },
{ “data”: “CreateUser”, “title”: “操作人”, “width”: “40px”, “render”: function (data, type, record) { if (record.ModifyUser != undefined && record.ModifyUser != “” && record.ModifyUser != null) return record.ModifyUser; else return data;
}
},
{ “data”: “CreateTime”, “title”: “操作时间”, “width”: “120px”, “render”: function (data, type, record) { if (record.ModifyTime != undefined && record.ModifyTime != “” && record.ModifyTime != null) data = record.ModifyTime; var dt = eval(“new “ + data.substr(1, data.length - 2)); return dt.Format(“yyyy-MM-dd hh:mm:ss”);
}
}
]
});

复制代码

从表面去看待事物视线总有点被层层薄雾笼罩的感觉,当你静下心来思考并让指尖飞梭于键盘之上,终将会拨开浓雾见青天。这是我切身体验。

在EF关系配置中,我暂且将主体对象称作为父亲,而依赖对象称作为孩子,父亲与孩子关联的关系可能是必须的也可能是可选的,如果是必须的那么意味着孩子不能因没有父亲而独立存在,又如果父亲被删除了(即父亲与孩子的关系被隔离),那么孩子将变成留守儿童(即孤儿),所以当处在这种情况下时,那么孩子应该需要自动被删除。

必须关系和可选关系

我们接下来就父亲与孩子的关联关系来进行删除的话题。

我们建立三个类,一个类是Student(学生类),一个类是Grade(成绩类),最后一个类是Flower(小红花类)。我们假设有如下场景:一个学生对应多门成绩,但一门成绩就属于一个学生,同时可能学生团队合作表现好,一朵小红花对应多个学生,但是这个小红花肯定只会被一个学生拿走也就只对应一个学生,也有可能没得到小红花。鉴于此,类建立如下:

复制代码

public class Student /\*学生类\*/
{ public int Id { get; set; } public string Name { get; set; } public int? FlowerId { get; set; } public virtual Flower Flower { get; set; } public virtual ICollection<Grade> Grades { get; set; }
} public class Grade /\*成绩类\*/
{ public int Id { get; set; } public int Fraction { get; set; }  /\*学生成绩\*/ public int StudentId { get; set; } public virtual Student Student { get; set; }
} public class Flower  /\*小红花类\*/
{ public int Id { get; set; } public string Remark { get; set; } /\*小红花描述\*/ public virtual ICollection<Student> Students { get; set; }
}

复制代码

通过上述描述,我们对应的映射如下:

学生映射:

复制代码

public class StudentMap : EntityTypeConfiguration { public StudentMap()
{
ToTable(“Student”);
HasKey(key => key.Id);
HasOptional(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId);

    }

}

复制代码

成绩映射:

复制代码

public class GradeMap: EntityTypeConfiguration { public GradeMap()
{
ToTable(“Grade”);
HasKey(p => p.Id);
HasRequired(p => p.Student).WithMany(p => p.Grades).HasForeignKey(p => p.StudentId);
}
}

复制代码

对于EF上下文建立,不再描述,不明白的话可以参见我前两篇文章。

对于我们上面的可选字段FlowerId生成数据库中也是可选的,如下:

 

我们插入数据如图:

现在我们进行如下操作:删除学生姓名为bob的 

using (var ctx = new EntityDbContext())
{
ctx.Set().Remove(ctx.Set().Single(p => p.Name == “bob”));
}

删除后结果如下:

那么问题来了,为什么我删除学生名为bob的而相关成绩也删除了呢?

答案是在学生和成绩之间建立了一个级联删除,所以会自动进行删除,级联删除也就是当父亲被删除时,其孩子也会被删除,EF Code  First为什么会这样做呢?因为学生和成绩之间的关系是必须(Required)的。

EF Code First不仅在实体在进行了配置而且在数据库中进行了配置,因为那是至关重要的,如果级联删除存在于实体中,那么在数据库中也应该必须存在,如果这两者不能同步那么在数据库中会出现约束错误。

接下来我们通过Flower(小花)来简介删除学生姓名为bob的,因为其对应的Remark是so bad(坏学生):

ctx.Set().Remove(ctx.Set().Include(p => p.Students).Single(p => p.Remark == “so bad”));

结果如下:

 

那么问题来了,为什么没有删除学生bob呢?

答案就是外键属性FlowerId和导航属性Flower被设置成了空,所以学生bob不会被删除,因为EF Code First不会为可选的关系设置级联删除。 

【注意】在此种情况下, 如果你加载学生集合列表到内存中,那么EF Code First会在保存之前将外键属性设置为空。

如果此时你想在可选关系上强制执行删除那就在映射中进行如下操作:

HasOptional(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId).WillCascadeOnDelete(true);

接下来如果我进行学生与小花之间的映射进行如下修改:

HasRequired(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId);

此时再来进行上述删除:  ctx.Set().Remove(ctx.Set().Include(p => p.Students).Single(p => p.Remark == “so bad”));  此时结果如下:

 

那么问题来了,为什么这样就能进行相应学生的删除了呢?

答案就是当你用上必须的关系(即Required)之后即使你设置外键属性可为空,但是当映射到数据库之后,它会将其映射为非空的外键字段(可以理解为关系映射比POCO实体手动设置优先级高)!不信看如下图:

小结

(1)当关系为可选(Optional)时此时外键属性和导航属性为空,不会进行级联删除,但是可以用 WillCascadeOnDelete 进行强制删除。

(2)当关系为必须(Required)时此时会内置进行级联删除即使外键属性为可空的类型,也就是说无需多此一举加上WillCascadeOnDelete来进行级联删除。

你是不是觉得关于删除就这么简单呢?那你就大错特错了,请继续看下文。

依然以上述为例,我们现在想象有这样一场景,bob的成绩太差每次都没及格,并且虽给了小红花但是评语写着so bad,这样放学回家如何向爸妈交代呢,至少将成绩考好点吧,于是它要求老师删除他不良的成绩并给其100分的好成绩。在此场景下,我们代码如下:

复制代码

        using (var ctx = new EntityDbContext())
        { var stu = ctx.Set<Student>().Single(p => p.Name == "bob");

            stu.Grades.Remove(stu.Grades.OrderBy(p \=> p.Id).First(p => p.Student.Name == "bob"));

            stu.Grades.Add(new Grade() { Fraction = 100 });
        }

复制代码

但结果是老师也是有心无力啊,出错了,如下:

因为成绩从导航属性集合中移出后,它变成孤立对象(外键为NULL),提交时,是因为外键约束而失败,异常提示,也显示外键不能为空!

所以此时我们能想到的办法就是直接将孩子进行删除或者通过重写SaveChanges找到并删除。

于是在保存之前我添加如下代码:

ctx.Set().Local.Where(p => p.Student == null).ToList().ForEach(r => ctx.Set().Remove(r));

重写SaveChanges

复制代码

    public override int SaveChanges()
    {
        ctx.Set<Grade>()
            .Local
            .Where(p \=> p.Student == null).ToList()
            .ForEach(r \=> ctx.Set<Grade>().Remove(r)); return base.SaveChanges();
    }

复制代码

最后通过,数据成功进行添加,如图:

上述代码有如下四点意思

(1)使用DbSet.Local来访问当前通过上下文追踪的没有运行任何数据库查询并且未被删除的成绩实体

(2)过滤列表中每一个没有引用学生实体的数据

(3)通过一个过滤列表的副本,来避免枚举时修改一个Collection

(4)标记每个孤儿(成绩)为已删除

小结

(1)默认情况下,EF Code First认为空的外键属性其关系是可选的,而对于非空的外键属性其关系是必须的。必须关系同时配置了级联删除,以至于如果父亲被删除则其所有的孩子也将被删除。

(2)必须和可选的关系自然能通过Fluent API来进行改变或者Data Anotaions和级联删除能够用Fluent API来进行配置

(3)如果父亲已经被隔离,那么通过级联删除不会删除孩子。

EF 那些琐事儿

上述异常信息被EF团队称作为“概念上可空消息”,因为当一个关系被隔离,则其关系中的外键将被设置为空。然而,如果属性为非空,那么EF在概念上将其设置为空,但是实际上没这么做,所以“概念上可空消息”没有被保存到数据库中而是在异常中。


为了方便大家在移动端也能看到我分享的博文,现已注册个人公众号,扫描上方左边二维码即可,欢迎大家关注,有时间会及时分享相关技术博文。感谢花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨打赏一下吧,让楼主能喝上一杯咖啡,在此谢过了!
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!
本文版权归作者和博客园共有,来源网址:http://www.cnblogs.com/CreateMyself)/欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。

背景

ENode是一个CQRS+Event Sourcing架构的开发框架,Event Sourcing需要持久化事件,事件可以持久化在DB,但是DB由于面向的是CRUD场景,是针对数据会不断修改或删除的场景,所以内部实现会比较复杂,性能也相对比较低。而Event Store实际上对数据只有新增和查询的需求,所以我想为Event Sourcing的场景针对性的实现一个Event Store。看了一下业界的一些实现,感觉都没有达到我的期望,所以想自己动手实现一个。下面是我构思的一个Event Store的单机版应该要具备的能力以及对应的设计方案,分享出来和大家讨论。

一、需求概述

  • 存储聚合根的事件数据
  • 支持事件的版本并发控制,新事件的版本号必须是当前版本号+1
  • 支持命令重复判断,即不可以处理重复命令产生的事件
  • 支持按聚合根ID查询该聚合根的所有事件
  • 支持按聚合根ID+事件版本号查询指定的事件
  • 支持按命令ID查询该命令对应的事件数据
  • 高性能,写入要尽量快,查询要尽量快

二、事件数据格式

复制代码

{ “aggregateRootId”: “”, //聚合根ID
“aggregateRootType”: “”, //聚合根类型
“eventVersion”: “”, //事件版本号
“eventTime”: “”, //事件发生时间
“eventData”: “”, //事件数据,JSON格式
“commandId”: “”, //产生该事件的命令ID
“commandTime”: “” //产生该事件的命令产生时间
}

复制代码

三、存储设计

1、核心内存存储设计

  • 遵循内存只存储索引数据的原则,尽量充分利用内存;
  • aggregateLatestVersionDict,存储每个聚合根的最大事件版本号
    • key:aggregateRootId,聚合根ID
    • value:
      • eventVersion,当前聚合根的最新事件的版本号,也即当前聚合根的版本号
      • eventTime,事件产生时间
      • eventPosition,事件在事件数据文件中的位置
  • commandIdDict,存储命令索引
    • key:commandId,命令ID
    • value:
      • commandTime,命令产生时间
      • eventPosition,命令对应的事件在事件数据文件中的位置

2、物理存储的数据

  • 事件数据:eventData,单条数据的结构:

复制代码

{ “aggregateRootId”: “”, //聚合根ID
“aggregateRootType”: “”, //聚合根类型
“eventVersion”: “”, //事件版本号
“eventTime”: “”, //事件发生时间
“eventData”: “”, //事件数据,JSON格式
“commandId”: “”, //产生该事件的命令ID
“commandTime”: “”, //产生该事件的命令产生的事件
“previousEventPosition”: “”//前一个事件在事件文件中的位置
}

复制代码

  • 事件索引:eventIndex,单条数据的结构:

{ “aggregateRootId”: “”, //聚合根ID
“eventVersion”: “”, //事件版本号
“eventTime”: “”, //事件产生时间
“eventPosition”: “”, //事件在事件数据文件中的位置
}

  • 命令索引:commandIndex,存储内容:存储所有命令的ID及其对应的事件所在文件的位置

{ “commandId”: “”, //聚合根ID
“commandTime”: “”, //命令产生时间
“eventPosition”: “”, //事件在事件数据文件中的位置
}

3、事件数据存储

  • 同步顺序写eventDataChunk文件,一个文件大小为1GB,写满一个文件后写入下一个文件;
  • 写入每个事件时,同时写入当前事件的前一个事件所在的文件位置,以便将来可以一次性将某个聚合根的所有事件从文件查找出来;

4、事件索引存储

  • 异步顺序写eventIndexChunk文件,一个文件大小为1GB,写满一个文件后写入下一个文件;
  • 对于已经写满的不会再变化的文件的内容,使用后台线程进行B+树索引整理,索引的排序依据是聚合根ID+事件版本号;B+树设计为3层,根节点包含1000个子节点,每个子节点再包含1000个子节点,这样叶子节点共有100W个。每个叶子节点我们保存20个版本索引,则单个文件共可保存最多2000W个版本索引,10个文件为2亿个版本索引;单机存储2亿个事件索引,应该可以满足大部分应用场景了;3层,则查找任意一个节点,只需要3次IO访问;
  • 由于是后台线程对已经写完的文件进行B+树索引整理,B+树是在内存建立,建立完成后,将最新的内容写入新文件,原子替换老的eventIndexChunk文件;所以,这块的逻辑处理应该不会对服务的主逻辑产生较大的影响;
  • 采用BloomFilter优化查询性能,使用BloomFilter来快速判断某个eventIndexChunk文件中是否包含某个聚合根ID,如果不在,则不用从B+树去检索该聚合根的版本号了;如果在,则取检索;通过这个设计,当我们要获取某个聚合根的最大版本号时,不需要对每个eventIndexChunk文件进行B+树查询,而是先通过BloomFilter快速判断当前的eventIndexChunk文件是否包含该聚合根的信息,大大提升检索效率;BloomFilter的二进制Bit数据占用内存小,可以在每个eventIndexChunk文件被扫描时,和文件头的信息一起加载到内存;

5、命令索引存储

  • 异步顺序写commandIndexChunk文件,一个文件大小为1GB,写满一个文件后写入下一个文件;
  • 同事件索引存储,进行B+树索引建立,索引的排序依据是命令ID;
  • 同事件索引存储,采用BloomFilter优化查询性能;

四、框架逻辑设计

1、查询某个聚合根的最大版本号

  • EventStore启动时,会加载所有的eventIndexChunk文件的元数据到内存,比如文件号、文件头、BloomFilter等信息,但不真实加载文件内容,文件数不会太多,最多也就几十个;
  • 根据聚合根ID+BloomFilter算法,快速确定应该到哪个eventIndexChunk文件中去查找该聚合根的最新版本号,eventIndexChunk文件从新到旧遍历,因为某个聚合根ID的最大版本号一定是在最新的eventIndexChunk文件中的;
  • 在找到的eventIndexChunk中使用B+树查找算法,找到对应的叶子节点;
  • 在找到的叶子节点,使用二分查找算法(由于单个节点的聚合根ID不多,顺序查找即可),找到指定聚合根的最新版本号;

2、查询某个聚合根的所有事件

  • 先通过上面的算法找出该聚合根的最大版本号的事件在事件数据文件中的位置;
  • 然后从该位置获取事件完整数据;
  • 再根据事件数据中记录的上一个事件在事件数据文件中的位置,查找上一个事件的数据;
  • 以此类推,直到找到该聚合根的第一个事件的数据;

3、查询某个命令对应的事件数据

  • 先尝试从内存查询该命令的索引信息,如果存在,则直接获取该命令对应的事件在事件数据文件中的位置,即eventPosition;如果不存在,则尝试从命令的索引文件中查找,结合BloomFilter和B+树查找算法进行查找;
  • 如果找到了eventPosition,则根据eventPosition到事件数据文件中查找对应的事件数据即可;如果未找到,则返回空;

4、追加一个新事件的处理逻辑

  • 根据aggregateLatestVersionDict判断事件版本号是否合法,必须是聚合根的当前版本号+1,如果当前版本号不存在,则首先尝试从eventIndexChunk文件查找当前聚合根的最大版本号,如果还是查找不到,说明当前聚合根确实不存在任何事件,则当前事件版本号必须为1;
  • 根据commandIdDict判断命令ID是否重复,如果commandIdDict中不存在该命令,尝试从commandIndexChunk文件中查找,也是B+树的方式;这里需要设计一个配置项,让开发者配置是否需要继续从commandIndexChunk文件查找命令ID。有时我们只希望从内存查找即可,不希望再从磁盘查找了,因为判断命令是否重复我们很多时候只希望检查最近一段时间内的命令,检查全部命令代价过大,意义也不是很大;
  • 如果事件的版本号合法、命令ID不重复,则Append的方式写入事件数据到eventDataChunk;
  • 写入完成后,更新aggregateLatestVersionDict、commandIdDict,、BloomFilter的Bit数组,以及将当前的事件放入内存的一个双缓冲队列;队列消费者异步批量将事件索引和命令索引写入对应的索引文件;
  • 返回事件写入结果;

5、其他逻辑

  • 异步线程定时批量持久化事件索引;
  • 异步线程定时批量持久化命令索引;
  • 异步线程定时清理不需要放在内存的聚合根最新版本号信息(aggregateLatestVersionDict中的key),根据eventTime判断,只保留最近1周有过变化(产生过事件)的聚合根;
  • 异步线程定时清理不需要放在内存的命令索引(commandIdDict中的key),根据commandTime判断,只保留最近1周的命令ID;
  • 异步线程定时进行事件索引和命令索引的B+树索引的建立,即对已经写入完成的eventIndexChunk和commandIndexChunk文件的内部重构;
  • eventIndexChunk和commandIndexChunk文件标记为写入完成前,要把BloomFilter的Bit数组内容写入文件中;
  • 其他EventStore的启动逻辑,比如启动时加载一定数量的索引数据到内存,以及索引数据相比事件数据是否有漏掉或无效的检查;
  • 其他逻辑支持,如支持聚合根的快照存储,从文件查找数据时,如果文件的B+树索引信息还未建立,则需要进行全文扫码;

fastDfs Code: https://code.google.com/p/fastdfs/

FastDfs_Client_DotNet:https://code.google.com/p/fastdfs/downloads/detail?name=FastDFS_Client_Dotnet.rar

初始化

1
2
3
4
5
List<IPEndPoint> trackerIPs = new List<IPEndPoint>();
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("192.168.0.123"),22122);
trackerIPs.Add(endPoint);
ConnectionManager.Initialize(trackerIPs);
StorageNode node = FastDFSClient.GetStorageNode("group1");

获取文件信息

1
2
3
4
5
FDFSFileInfo fileInfo = FastDFSClient.GetFileInfo(node, fileName);
Console.WriteLine(string.Format("FileName:{0}", fileName));
Console.WriteLine(string.Format("FileSize:{0}", fileInfo.FileSize));
Console.WriteLine(string.Format("CreateTime:{0}", fileInfo.CreateTime));
Console.WriteLine(string.Format("Crc32:{0}", fileInfo.Crc32));

上传文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
byte[] buffer = FastDFSClient.DownloadFile(node, fileName, 0L, 0L);
string localName = @"D:\a.ppt";
if (fileInfo.FileSize >= 1024)
{
FileStream fs = new FileStream(localName, FileMode.OpenOrCreate, FileAccess.Write);

long offset = 0;
long len = 1024;
while (len > 0)
{
byte[] buffer = new byte[len];
buffer = FastDFSClient.DownloadFile(node, fileName, offset, len);
fs.Write(buffer, 0, int.Parse(len.ToString()));
fs.Flush();

offset = offset + len;
len = (fileInfo.FileSize - offset) >= 1024 ? 1024 : (fileInfo.FileSize - offset);
}
fs.Close();

}
else
{
byte[] buffer = new byte[fileInfo.FileSize];
buffer = FastDFSClient.DownloadFile(node, fileName);
FileStream fs = new FileStream(localName, FileMode.OpenOrCreate, FileAccess.Write);
fs.Write(buffer, 0, buffer.Length);
fs.Flush();
fs.Close();
}

删除文件

1
FastDFSClient.RemoveFile("group1", fileName);

HTTP获取文件
1. Http流读取

1
2
3
4
5
string url = "http://aa.com/da/g5/M02/0D/16/rBEDik_nOJ0IAAAAAAA_cbJCY-UAACrRgMhVLEAAD-J352.jpg";
System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);
System.Net.HttpWebResponse res = (System.Net.HttpWebResponse)req.GetResponse();
Image myImage = Image.FromStream(res.GetResponseStream());
myImage.Save("c:\\fast.jpg");//保存

>

WebClient直接下载

1
2
3
4
5
6

using (WebClient web = new WebClient())
{
web.DownloadFile("http://img13.360buyimg.com/da/g5/M02/0D/16/rBEDik_nOJ0IAAAAAAA_cbJCY-UAACrRgMhVLEAAD-J352.jpg", "C:\\abc.jpg");
web.DownloadFile("http://192.168.81.233/M00/00/00/wKhR6VADbNr5s7ODAAIOGO1_YmA574.jpg", "C:\\abc.jpg");
}


目录

  • Elasticsearch+kibana
    • 环境搭建
      • windows 10环境配置
      • 安装Elasticsearch
      • head安装(非必需)
      • 安装kibana
    • 基本概念
      • Index
      • Type
      • Document
    • DSL的基本使用
      • 增加
      • 修改
      • 查询
      • 删除
  • Elasticsearch .Net
    • Low level client基本使用
    • 项目实战
  • 总结
  • 参考

     Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最
先进、性能最好的、功能最全的搜索引擎库。

     一说到全文搜索,lucene久负盛名。早年间,因为项目需要,接触过一个叫盘古分词的开源项目,借助其中的分词实现了分词搜索的功能。而盘古分词就是lucence的.NET版本。据说这个开源项目已经恢复更新并支持. NET Core,有兴趣的童鞋可以去围观一下(https://github.com/LonghronShen/Lucene.Net.Analysis.PanGu/tree/netcore2.0)。

      我想很多童鞋都听过ELK,ELK是Elasticsearch、Logstash、Kibana。正好公司运维同事引入了这样一套体系,用于建立集中式日志收集系统,将所有节点上的日志统一收集,管理,访问。虽然能够从一定程度上解决基本的问题,但是原生的kibana界面和查询方式都不够友好,很难推向广大的开发人员。于是我在想,我们是否可以利用这个开源的库集成到运维自动化平台当中,让这把利剑发挥出更大的价值。

一、环境搭建  

本文是基于windows 10操作系统的es环境的搭建。

  1. java环境安装

     由于es是java语言开发的,所以这里要安装java环境。

     jdk下载:

https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

     安装完成之后就是配置环境变量:

     查看是否安装成功:

2.安装Elasticsearch

    Elasticsearch版本已经比较多,初学者可能比较懵。特别是在安装head和Kibana的时候,如果版本不匹配,往往会导致无法使用。这里使用的是elasticsearch-5.6.11版本。

     elasticsearch-5.6.11下载:

https://www.elastic.co/downloads/past-releases/elasticsearch-5-6-11

解压到C:\ELk 备用。

3.head安装(非必需)

    es 4.x 版本安装head很简单,只需下载head插件解压到指定目录即可。es 5.x+需要借助node安装。

     head下载:

https://github.com/mobz/elasticsearch-head

解压到C:\ELk\elasticsearch-5.6.11

     node下载:

https://nodejs.org/dist/v8.12.0/node-v8.12.0-win-x64.zip

安装node

检查node和npm是否安装成功

path环境变量末尾 会自动增加 C:\Program Files\nodejs\

安装 phantomjs
官网:http://phantomjs.org/下载【配置环境变量】

安装grunt
npm install -g grunt-cli

执行C:\ELk\elasticsearch-5.6.11\bin\elasticsearch.bat

执行命令启动 head

浏览器访问:http://localhost:9100/

4.安装kibana

    导致为止,其实elasticsearch自身已经安装完成。通过Head就能很方便的操作es,但是kibana集成了head类似功能,并提供了更加友好的访问界面。

     kibana-5.6.9-windows-x86下载:

https://www.elastic.co/downloads/past-releases/kibana-5-6-9

下载之后,解压到C:\ELk\kibana-5.6.9-windows-x86

执行C:\ELk\kibana-5.6.9-windows-x86\bin\kibana.bat

浏览器访问:http://localhost:5601


二、基本概念

  • Cluster(集群)

         集群是一个或多个节点(服务器)的集合,这些节点一起保存整个数据,并在所有节点上提供联合索引和搜索功能。

         一个运行中的 Elasticsearch 实例称为一个 节点,而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。

          作为用户,我们可以将请求发送到 集群中的任何节点 ,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。

  • Node(节点)

    节点是集群的一部分、存储数据并参与集群的索引和搜索功能的单个服务器。

  • Index

    索引是具有相似特性的文档集合。

    • 类似于关系型数据库中”“的概念
  • Type

    Type是具有一组公共字段的文档定义类型

         例如,假设您运行一个博客平台并将所有数据存储在一个索引中。在该索引中,可以定义用户数据的类型、博客数据的另一种类型以及注释数据的另一种类型。

    • 类似于关系型数据库中”“的概念
  • Document

    被索引信息的基本单元。

    • 类似于关系型数据库的一个记录(行)
    • 会被压缩成json格式
  • Shards & Replicas(分片&副本分片)

    索引可以潜在地存储可以超过单个节点的硬件限制的大量数据。例如,占用1TB磁盘空间的十亿个文档的单个索引可能不适合单个节点的磁盘,或者可能太慢而无法单独为来自单个节点的搜索请求提供服务。

         分片的两个主要原因:

    • 它允许您水平分割/缩放您的内容卷。
    • 它允许你分配和并行操作的碎片(可能在多个节点上)从而提高性能/吞吐量

    在网络/云环境中,在任何时候都可以预期到故障,在碎片/节点不知何故脱机或由于任何原因消失的情况下,非常有用,并且强烈建议使用故障转移机制。为此,Elasticsearch允许您将一个或多个索引碎片的副本复制到称为副本碎片(replica shards)或简称为副本(replica)中。

         复制是重要的两个主要原因:

    • 在碎片/节点失败的情况下,它提供了高可用性。由于这个原因,需要注意的是,副本碎片永远不会分配到与原始/主碎片相同的节点上。
    • 它允许您扩展搜索量/吞吐量,因为可以并行地在所有副本上执行搜索。

         添加故障转移

    当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。

         拥有两个节点的集群——所有主分片和副本分片都已被分配。

三、DSL的基本使用

elasticsearch也像mysql一样提供了专门的语法来操作数据。Elasticsearch provides a full Query DSL (Domain Specific Language) based on JSON to define queries.

  • 创建文档
1
2
3
4
5
6
PUT people/person/1?op_type=create
{
"user" : "kimchy",
"post_date" : "2009-11-15T14:12:12",
"message" : "trying out Elasticsearch"
}
  • 修改
1
2
3
4
5
6
POST /user/guest/20/_update
{
"doc": {
"RealName":"LukyHuu20"
}
}
  • 查询
1
2
3
4
5
6
7
8
GET /user/guest/_search
{
"query": {
"match": {
"Id":22
}
}
}
  • 删除
1
2
3
4
DELETE /user/guest/15
{

}

四、Elasticsearch .Net

     elasticsearch是以restfulAPI方式对外提供接口,并提供客户端给多种语言使用。Elasticsearch uses standard RESTful APIs and JSON. We also build and maintain clients in many languages such as Java, Python, .NET, SQL, and PHP. Plus, our community has contributed many more. They’re easy to work with, feel natural to use, and, just like Elasticsearch, don’t limit what you might want to do with them.
参考(https://www.elastic.co/products/elasticsearch)

1.Low level client基本使用

     本文是介绍ES的.NET客户端,Elasticsearch .Net - Low level client[5.x]

通过引入对应的版本的客户端,便可通过C#操作ES。参考(https://www.elastic.co/guide/en/elasticsearch/client/net-api/5.x/elasticsearch-net.html)

连接

1
2
3
4
var settings = new ConnectionConfiguration(new Uri("http://example.com:9200"))
.RequestTimeout(TimeSpan.FromMinutes(2));

var lowlevelClient = new ElasticLowLevelClient(settings);

插入文档

1
2
var indexResponse = lowlevelClient.Index<byte[]>("user", "guest", user.Id.ToString(), user);
byte[] responseBytes = indexResponse.Body;

更新文档

1
2
3
4
5
6
7
8
9
10
var searchResponse = lowlevelClient.Update<string>("user", "guest", id.ToString(), new
{
doc = new
{
RealName = realname,
Description = description
}
});

bool successful = searchResponse.Success;

查询

1
2
3
4
5
6
7
8
9
10
11
12
var searchResponse = lowlevelClient.Search<string>("user", "guest", new
{
query = new
{
match = new
{
Id = id
}
}
});

bool successful = searchResponse.Success;

删除

1
2
3
var searchResponse = lowlevelClient.Delete<string>("user", "guest", id.ToString());

bool successful = searchResponse.Success;

2.项目实战

     前面大致介绍了ES的安装和基本使用。那么,如何在项目中落地呢?

使用nuget安装Elasticsearch.Net 5.6.4

Install-Package Elasticsearch.Net -Version 5.6.4

安装完后,

基本的增删该查在项目中的实现上面已经有所介绍,这里重点讲一下查询:

笔者使用的.NET MVC5 Web框架,对于返回的结果笔者做了一个简单封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

public class ESearchRoot<T>
{



public int took { get; set; }



public string timed_out { get; set; }



public _shards _shards { get; set; }



public Hits<T> hits { get; set; }
}

public class _shards
{



public int total { get; set; }



public int successful { get; set; }



public int skipped { get; set; }



public int failed { get; set; }
}

public class HitsItem<T>
{



public string _index { get; set; }



public string _type { get; set; }



public string _id { get; set; }



public string _score { get; set; }



public T _source { get; set; }



public List<int> sort { get; set; }



public Highlight highlight { get; set; }
}

public class Hits<T>
{



public int total { get; set; }



public string max_score { get; set; }



public List<HitsItem<T>> hits { get; set; }
}

public class Highlight
{



public List<string> Description { get; set; }
}

因为soure返回的对象是不定的,所以使用了泛型。
本项目soure对应的类,user:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60




public class User
{



public string Account { get; set; }



public string Phone { get; set; }



public string Email { get; set; }



public string RealName { get; set; }



public string CanReview { get; set; }



public string CanExcute { get; set; }



public string Avatar { get; set; }



public string IsUse { get; set; }



public int Id { get; set; }



public string Name { get; set; }



public string Description { get; set; }



public DateTime CreateTime { get; set; }



public DateTime ModifyTime { get; set; }
}

项目使用了带条件的分页查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
public List<AdminUser> GetBySomeWhere(string keyword, int limit, int pageSize, out int total)
{
List<AdminUser> users = new List<AdminUser>();

total = 0;
try
{
var settings = new ConnectionConfiguration(new Uri("http://localhost:9200/"))
.RequestTimeout(TimeSpan.FromMinutes(2));

var lowlevelClient = new ElasticLowLevelClient(settings);


var request = new object();
if (!String.IsNullOrEmpty(keyword))
{
request = new
{
from = limit,
size = pageSize,
query = new
{
match = new
{
Description = keyword
}
},
highlight = new
{
fields = new
{
Description = new { }
}
},
sort = new
{
Id = new
{
order = "desc"
}
}
};
}
else
{
request = new
{
from = limit,
size = pageSize,
query = new
{
match_all = new
{

}
},
highlight = new
{
fields = new
{
Description = new { }
}
},
sort = new
{
Id = new
{
order = "desc"
}
}
};
}


var searchResponse = lowlevelClient.Search<string>("user", "guest", request);

bool successful = searchResponse.Success;
var responseJson = searchResponse.Body;

if (!successful)
{
return users;
}

ESearchRoot<User> root = JsonHelper.JSONStringObject<ESearchRoot<User>>(responseJson);
if (root != null)
{
total = root.hits.total;
foreach (HitsItem<User> item in root.hits.hits)
{
if (item._source != null)
{
string highlightDescription = String.Empty;
StringBuilder sbDs = new StringBuilder();
if (item.highlight != null && item.highlight.Description.Count > 0)
{

foreach (var d in item.highlight.Description)
{
sbDs.Append(d);
}
highlightDescription = sbDs.ToString();
}

AdminUser user = new AdminUser
{
Id = item._source.Id,
RealName = item._source.RealName,
Account = item._source.Account,
Email = item._source.Email,
Phone = item._source.Phone,

Avatar = item._source.Avatar,
Description = item._source.Description,
HighlightDescription = highlightDescription,
CreateTime = item._source.CreateTime,
ModifyTime = item._source.ModifyTime
};
users.Add(user);
}
}
}

return users;
}
catch (ElasticsearchClientException ex)
{

}
return users;
}

项目最终的效果如下:

五、总结

     elasticsearch是很强大的开源工具,在实现全文搜索上有其独到之处,也是大数据的分析方面利器,值得大家深入去研究和实践。

六、参考


原文地址:http://dotnetspeak.com/index.php/2011/03/entity-framework-code-first-caching/

  最近团队改为Entity FrameWork 和ASP.NET MVC进行项目开发,为了提高访问速度必须缓存EF的查询结果,在网上查找了两种Cache的缓存方法,一种是基于EF Caching with Jarek Kowalski’s Provider,博客园中已经有很多前辈们已经有了详细的介绍。还有一种方法,就是本文作者的实现方法,没有找到园子里有人翻译,就作为开博第一篇吧。

  第一种方法在Code First实现下会有小问题,改造后也可以应用,稍后再整理。

  原文作者需要实现一个方法明确哪些内容需要缓存,定义如下的扩展方法:

复制代码

    public static IEnumerable<T> AsCacheable<T>(this IQueryable<T> query)  
    {  
        if (cacheProvider == null)  
        {  
            throw new InvalidOperationException("Please set cache provider (call SetCacheProvider) before using caching");  
        }  
        return cacheProvider.GetOrCreateCache<T>(query);  
    }

复制代码

  最终的调用代码如下:

EFCacheExtensions.SetCacheProvider(MemoryCacheProvider.GetInstance());
using (ProductContext context = new ProductContext())
{
var query = context.Products.OrderBy(one => one.ProductNumber).
Where(one => one.IsActive).AsCacheable();
}

很简单吧。

  在下面的例子里,作者实现了一个基于内存的 Cache Provider。使用全局静态变量来实现缓存。

作者定义了如下接口:

复制代码

public interface IEFCacheProvider

 {

     IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query);

     IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query, TimeSpan cacheDuration);

     bool RemoveFromCache<T>(IQueryable<T> query);

 }

复制代码

下面的关键的实现代码,首先看Memory Provider实现

View Code

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Collections.Concurrent;

namespace EFCodeFirstCacheExtensions

{

 public class MemoryCacheProvider : IEFCacheProvider

 {

     private MemoryCacheProvider() { }

       public static MemoryCacheProvider GetInstance()

     {

         lock (locker)

         {

             if (dictionary == null)

             {

                 dictionary = new ConcurrentDictionary<string, CacheItem>();

             }

               if (instance == null)

             {

                 instance = new MemoryCacheProvider();

             }

         }

         return instance;

     }

       private static ConcurrentDictionary<string, CacheItem> dictionary;

     private static MemoryCacheProvider instance;

     private static object locker = new object();

       public IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query, TimeSpan cacheDuration)

     {

         string key = GetKey<T>(query);

           CacheItem item = dictionary.GetOrAdd(

             key,

             (keyToFind) => { return new CacheItem() 

                { Item = query.ToList(), AdditionTime = DateTime.Now }; });

           if (DateTime.Now.Subtract(item.AdditionTime) > cacheDuration)

         {

             item = dictionary.AddOrUpdate(

                 key,

                 new CacheItem() { Item = item.Item, AdditionTime = DateTime.Now },

                 (keyToFind, oldItem) => { return new CacheItem() 

                    { Item = query.ToList(), AdditionTime = DateTime.Now }; });

         }

         foreach (var oneItem in ((List<T>)item.Item))

         {

             yield return oneItem;

         }

     }

       public IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query)

     {

         string key = GetKey<T>(query);

           CacheItem item = dictionary.GetOrAdd(

             key,

             (keyToFind) => { return new CacheItem() 

                { Item = query.ToList(), AdditionTime = DateTime.Now }; });

           foreach (var oneItem in ((List<T>)item.Item))

         {

             yield return oneItem;

         }

     }

       public bool RemoveFromCache<T>(IQueryable<T> query)

     {

         string key = GetKey<T>(query);

         CacheItem item = null;

         return dictionary.TryRemove(key, out item);

     }

       private static string GetKey<T>(IQueryable<T> query)

     {

         string key = string.Concat(query.ToString(), "\\n\\r", 

            typeof(T).AssemblyQualifiedName);

         return key;

     }

 }

}

Memory Provider 实现了IEFCacheProvider 接口,并实现了自动过期和不过期缓存。作者使用了EF Code First 的一个实用的功能 IQueryable 的ToString() 方法,得到执行的T-SQL语句。并且将SQL语句结合类名(查询语句返回的泛型结果T)作为缓存的KEY(这里有bug 下面有解决方案)。在执行插入缓存前执行了ToList 方法,因为Entity FrameWork是延迟执行的,直至调用结果前,查询不会执行。

接下来是AppFabric provider 的实现,要想使用AppFabric,首先要在本机安装AppFabric服务,然后添加以下引用:

Microsoft.ApplicationServer.Caching.Client

Microsoft.ApplicationServer.Caching.Core

下面是AppFabric 的实现

View Code

public class AppFabricCacheProvider : IEFCacheProvider  
{  
    private AppFabricCacheProvider() { }

    private static object locker = new object();  
    private static AppFabricCacheProvider instance;  
    private static DataCache cache;

    public static AppFabricCacheProvider GetInstance()  
    {  
        lock (locker)  
        {  
            if (instance == null)  
            {  
                instance = new AppFabricCacheProvider();  
                DataCacheFactory factory = new DataCacheFactory();  
                cache = factory.GetCache("Default");  
            }  
        }  
        return instance;  
    }  
    public override IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query, TimeSpan cacheDuration)  
    {  
        string key = GetKey<T>(query);

        var cacheItem = cache.Get(key);  
        if (cacheItem == null)  
        {  
            cache.Put(key, query.ToList(), cacheDuration);  
            foreach (var oneItem in query)  
            {  
                yield return oneItem;  
            }  
        }  
        else  
        {  
            foreach (var oneItem in ((List<T>)cacheItem))  
            {  
                yield return oneItem;  
            }  
        }  
    }

    public override IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query)  
    {  
        string key = GetKey<T>(query);

        var cacheItem = cache.Get(key);  
        if (cacheItem == null)  
        {  
            cache.Put(key, query.ToList());  
            foreach (var oneItem in query)  
            {  
                yield return oneItem;  
            }  
        }  
        else  
        {  
            foreach (var oneItem in ((List<T>)cacheItem))  
            {  
                yield return oneItem;  
            }  
        }  
    }

    public override bool RemoveFromCache<T>(IQueryable<T> query)  
    {  
        string key = GetKey<T>(query);  
        CacheItem item = null;  
        return cache.Remove(key);  
    }

}

AppFabric 中已经内置了缓存依赖,不需要再自己计算缓存时间。上面使用DataCacheFactory方法来创建一个名为“Default”的实例。

单元测试中的AppFabric 的配置文件:

View Code

<configuration>

<configSections>
<section name=“dataCacheClient” type=“Microsoft.ApplicationServer.Caching.DataCacheClientSection, Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35” allowLocation=“true” allowDefinition=“Everywhere”/>
</configSections>

<dataCacheClient\>  
    <hosts\>  
        <host name\="SERGEYB-PC1" cachePort\="22233"/>  
    </hosts\>  
    <localCache isEnabled\="true" sync\="TimeoutBased" objectCount\="100000" ttlValue\="300" />

</dataCacheClient\>

<connectionStrings\>  
    <add name\="ProductConnection" connectionString\="Server=(local);Database=Products;Trusted\_Connection=True;" providerName\="System.Data.SqlClient"/>  
</connectionStrings\>

</configuration>

单元测试代码:

View Code

[TestMethod]

     public void MemoryCacheProviderGetOrCreateCacheUsageTest()

     {

         EFCacheExtensions.SetCacheProvider(MemoryCacheProvider.GetInstance());

         using (ProductContext context = new ProductContext())

         {

             var query = context.Products

                 .OrderBy(one => one.ProductNumber)

                 .Where(one => one.IsActive).AsCacheable();

               Assert.AreEqual(2, query.Count(), "Should have 2 rows");

               SQLCommandHelper.ExecuteNonQuery("Update Products Set IsActive = 0");

               query = context.Products

                 .OrderBy(one => one.ProductNumber)

                 .Where(one => one.IsActive).AsCacheable();

             Assert.AreEqual(2, query.Count(), "Should have 2 rows");

                 IQueryable<Product> cleanupQuery = context.Products

                 .OrderBy(one => one.ProductNumber)

                 .Where(one => one.IsActive);

               EFCacheExtensions.RemoveFromCache<Product>(cleanupQuery);

               query = context.Products

                 .OrderBy(one => one.ProductNumber)

                 .Where(one => one.IsActive).AsCacheable();

             Assert.AreEqual(0, query.Count(), "Should have 0 rows");

               EFCacheExtensions.RemoveFromCache<Product>(cleanupQuery);

         }

       }

 源代码下载地址:http://google.proxysec.com/baidu.com.php?u=a5c236e4cca5cc48bd8aOi8vZG90bmV0c3BlYWsuY29tL0Rvd25sb2Fkcy9FRkNvZGVGaXJzdENhY2hlLnppcA%3D%3D&b=1

上面的缓存Key生成代码有个BUG ,SQL查询的参数没有作为缓存Key的一部分,因此会导致查询结果一致,如下代码

复制代码

var isActive = true;
var query = context.Products
.OrderBy(one => one.ProductNumber)
.Where(one => one.IsActive == isActive).AsCacheable();

var isActive = false;
var query = context.Products
.OrderBy(one => one.ProductNumber)
.Where(one => one.IsActive == isActive).AsCacheable();

复制代码

查询结果相同。

stackoverflow上的解决方法 http://stackoverflow.com/questions/8275881/generating-cache-keys-from-iqueryable-for-caching-results-of-ef-code-first-queri

 原来的缓存KEY生成方法很简单:

private static string GetKey(IQueryable query)
{
string key = string.Concat(query.ToString(), “\n\r”,
typeof(T).AssemblyQualifiedName);
return key;
}

改进后的:

复制代码

public static string GetKey(IQueryable query)
{
var keyBuilder = new StringBuilder(query.ToString());
var queryParamVisitor = new QueryParameterVisitor(keyBuilder);
queryParamVisitor.GetQueryParameters(query.Expression);
keyBuilder.Append(“\n\r”);
keyBuilder.Append(typeof (T).AssemblyQualifiedName);

 return keyBuilder.ToString();   

}

复制代码

QueryParameterVisitor 的实现方法:

View Code

///

/// subclass which encapsulates logic to /// traverse an expression tree and resolve all the query parameter values ///
internal class QueryParameterVisitor : ExpressionVisitor
{
public QueryParameterVisitor(StringBuilder sb)
{
QueryParamBuilder = sb;
Visited = new Dictionary<int, bool>();
}

 protected StringBuilder QueryParamBuilder { get; set; }   
protected Dictionary<int, bool\> Visited { get; set; } 

 public StringBuilder GetQueryParameters(Expression expression)   
{   
    Visit(expression);   
    return QueryParamBuilder;   
} 

 private static object GetMemberValue(MemberExpression memberExpression, Dictionary<int, bool\> visited)   
{   
    object value;   
    if (!TryGetMemberValue(memberExpression, out value, visited))   
    {   
        UnaryExpression objectMember = Expression.Convert(memberExpression, typeof (object));   
        Expression<Func<object\>> getterLambda = Expression.Lambda<Func<object\>>(objectMember);   
        Func<object\> getter = null;   
        try   
        {   
            getter = getterLambda.Compile();   
        }   
        catch (InvalidOperationException)   
        {   
        }   
        if (getter != null) value = getter();   
    }   
    return value;   
} 

 private static bool TryGetMemberValue(Expression expression, out object value, Dictionary<int, bool\> visited)   
{   
    if (expression == null)   
    {   
        // used for static fields, etc   
        value = null;   
        return true;   
    }   
    // Mark this node as visited (processed)   
    int expressionHash = expression.GetHashCode();   
    if (!visited.ContainsKey(expressionHash))   
    {   
        visited.Add(expressionHash, true);   
    }   
    // Get Member Value, recurse if necessary   
    switch (expression.NodeType)   
    {   
        case ExpressionType.Constant:   
            value = ((ConstantExpression) expression).Value;   
            return true;   
        case ExpressionType.MemberAccess:   
            var me = (MemberExpression) expression;   
            object target;   
            if (TryGetMemberValue(me.Expression, out target, visited))   
            {   
                // instance target   
                switch (me.Member.MemberType)   
                {   
                    case MemberTypes.Field:   
                        value = ((FieldInfo) me.Member).GetValue(target);   
                        return true;   
                    case MemberTypes.Property:   
                        value = ((PropertyInfo) me.Member).GetValue(target, null);   
                        return true;   
                }   
            }   
            break;   
    }   
    // Could not retrieve value   
    value = null;   
    return false;   
} 

 protected override Expression VisitMember(MemberExpression node)   
{   
    // Only process nodes that haven't been processed before, this could happen because our traversal // is depth-first and will "visit" the nodes in the subtree before this method (VisitMember) does   
    if (!Visited.ContainsKey(node.GetHashCode()))   
    {   
        object value = GetMemberValue(node, Visited);   
        if (value != null)   
        {   
            QueryParamBuilder.Append("\\n\\r");   
            QueryParamBuilder.Append(value.ToString());   
        }   
    } 

     return base.VisitMember(node);   
}   

}

没有怎么翻译里面的内容,相信大牛们也不需要看我这稀烂的文笔。

  上一篇文章我们讲解了如何用 Fluent API 来配置/映射属性和类型,本文将把重点放在其是如何配置关系的。

  文中所使用代码如下

public class Student
{ public int ID { get; set; } public string Name { get; set; } public DateTime EnrollmentDate { get; set; } // Navigation properties
public virtual Address Address { get; set; } public virtual OtherInfo OtherInfo { get; set; } public virtual ICollection Enrollments { get; set; }
} public class Department
{ public Department()
{ this.Courses = new HashSet();
} // Primary key
public int DepartmentID { get; set; } public string Name { get; set; } public decimal Budget { get; set; } public System.DateTime StartDate { get; set; } public int? Administrator { get; set; } // Navigation property
public virtual ICollection Courses { get; private set; }
} public class Course
{ public int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } // Foreign key
public int DepartmentID { get; set; } public string DepartmentName { get; set; } public int SomeDepartmentID { get; set; } // Navigation properties
public virtual Department Department { get; set; } public virtual ICollection Enrollments { get; set; } public virtual ICollection Instructors { get; set; }
} public class Instructor
{ public int InstructorID { get; set; } public string Name { get; set; } public DateTime HireDate { get; set; } // Navigation properties
public virtual ICollection Courses { get; set; }
} public class Enrollment
{ public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } // Navigation property
public virtual Course Course { get; set; } public virtual Student Student { get; set; }
} public class Address
{ public int AddressId { get; set; } public string HomeAddress { get; set; } public string LiveAddress { get; set; } // Navigation property
public virtual Student Student { get; set; }
} public class OtherInfo
{ public int Id { get; set; } public string HomeAddress { get; set; } public string MailAddress { get; set; } public string PhoneNumber { get; set; } public string StudentID { get; set; } // Navigation property
public virtual Student Student { get; set; }
}

View Code

EntityTypeConfiguration

上面是泛型类的部分方法截图,一般我们通过 Code First Fluent API 来配置实体间的关系时都是从此泛型类的实例开始,此泛型类为我们提供了大部分的关系处理方法,如必须的 HasRequired ,可选的 _HasOptional ,_多个的 HasMany .

上面所有方法(除了 HasMany, HasOptional, HasRequired )的返回值都是泛型类 EntityTypeConfiguration 的实例本身,这给链式操作提供了极大地方便.

  HasRequired, HasOptional, HasMany 这三个方法的参数都是一个 lambda expression, 只不过前两个用于表示实体关系(_Relationship_)间的导航属性(_navigation property_),后一个则是导航属性的集合(_Collection_)

  HasRequired, HasOptional, HasMany 这三个方法的返回值都是可以继续进行配置的泛型类实例,虽然名称不同,方法名也略有不同,但方法主体所体现的思想是一致的

RequiredNavigationPropertyConfiguration<TEntityType, TTargetEntityType>

OptionalNavigationPropertyConfiguration<TEntityType, TTargetEntityType>

ManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType>

  三个泛型类都有类似于如下三个方法 WithRequired, WithOptional, WithMany, 这三个方法都提供了重载的无参版本,有参方法的参数也都是一个 lambda expression, 前两个用于表示实体关系(_Relationship_)间的导航属性(_navigation property_),后一个则是导航属性的集合(_Collection_)

  接下来,你可以使用方法 HasForeignKey 继续配置外键属性,方法的参数仍然是一个 lambda expression, 只不过代表一个属性

DependentNavigationPropertyConfiguration

public CascadableNavigationPropertyConfiguration HasForeignKey(Expression<Func<TDependentEntityType, TKey>> foreignKeyExpression);

  其它两个类主要包含方法 Map ,如下

ForeignKeyNavigationPropertyConfiguration

public CascadableNavigationPropertyConfiguration Map(Action configurationAction);

ManyToManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType>

public ManyToManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType> Map(Action configurationAction); public ManyToManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType> MapToStoredProcedures(); public ManyToManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType> MapToStoredProcedures(Action<ManyToManyModificationStoredProceduresConfiguration<TEntityType, TTargetEntityType>> modificationStoredProcedureMappingConfigurationAction);

  我们可以继续在返回的实例上进行配置

CascadableNavigationPropertyConfiguration

public void WillCascadeOnDelete(); public void WillCascadeOnDelete(bool value);

  我们看到了级联删除

Configuring Relationships

1:1,0 - Configuring a Required-to-Optional Relationship (One-to–Zero-or-One)

  一个学生可以有一个或没有其它信息(包括邮件地址、手机号等)

// Map one-to-zero or one relationship
modelBuilder.Entity()
.HasRequired(t => t.Student)
.WithOptional(t => t.OtherInfo);

1:1 - Configuring a Relationship Where Both Ends Are Required (One-to-One)

  一个学生肯定有一个地址信息(包含家庭住址、居住地址)

// Map one-to-one relationship
modelBuilder.Entity

()
.HasRequired(t => t.Student)
.WithRequiredPrincipal(t => t.Address);

1:N - Configuring a Required-to-Many Relationship (One-to-Many)

  一个学生可以参加多门课程

// Map one-to-many relationship
modelBuilder.Entity()
.HasMany(t => t.Enrollments)
.WithRequired(t => t.Student);

N:N - Configuring a Many-to-Many Relationship (Many-to-Many)

  一个老师可以教授多门课程,一门课程也可以由多名老师教授

// Map one-to-many relationship
modelBuilder.Entity()
.HasMany(t => t.Instructors)
.WithMany(t => t.Courses);

  你还可以进一步指定中间连接表(数据库将会创建中间连接表)

复制代码

// Map one-to-many relationship
modelBuilder.Entity()
.HasMany(t => t.Instructors)
.WithMany(t => t.Courses)
.Map(m => {
m.ToTable(“CourseInstructor”);
m.MapLeftKey(“CourseID”);
m.MapRightKey(“InstructorID”);
});

复制代码

Configuring a Relationship with One Navigation Property - 基于导航属性配置关系

  如果关系是单向的,即两个实体间只在一个实体上定义导航属性。Code First Conventions 能够自动推断这种关系为 one-to-many .

  例如你想在 Student 和 Address 两个实体间建立 one-to-one 关系,而且只在 Address 实体上包含导航属性,此时你就需要用 Code First Fluent API 配置这种关系

// Map one-to-one relationship
modelBuilder.Entity

()
.HasRequired(t => t.Student)
.WithRequiredPrincipal();

WillCascadeOnDelete - Enabling Cascade Delete (级联删除)

  你可以使用 WillCascadeOnDelete 来级联删除关系,如果从属主体上的外键是 not nullable, 那么 Code First 将设置级联删除,否则将不会设置级联删除,而只是仅仅把外键设置成 null

Code First Conventions 下是这样移除级联删除的

modelBuilder.Conventions.Remove()
modelBuilder.Conventions.Remove()

  Code First Fluent API 的方式如下

// Cascade Delete
modelBuilder.Entity()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);

Configuring a Composite Foreign Key - 配置组合外键

如果设置 Department 的主键为组合主键 _DepartmentID, Name,_则可以通过 Fluent API 为 Course 指定组合外键

复制代码

// Composite primary key
modelBuilder.Entity()
.HasKey(d => new { d.DepartmentID, d.Name }); // Composite foreign key
modelBuilder.Entity()
.HasRequired(c => c.Department)
.WithMany(d => d.Courses)
.HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });

复制代码

Renaming a Foreign Key That Is Not Defined in the Model - 重命名外键

  可以重命名外键名

// Renaming a Foreign Key That Is Not Defined in the Model
modelBuilder.Entity()
.HasRequired(c => c.Department)
.WithMany(t => t.Courses)
.Map(m => m.MapKey(“ChangedDepartmentID”));

Configuring a Foreign Key Name That Does Not Follow the Code First Convention

  如果从属实体上的外键属性名不符合 Code First Conventions 的规范,意即从属实体上没有外键,你可以通过如下方式来指定

// Configuring a Foreign Key Name That Does Not Follow the Code First Convention
modelBuilder.Entity()
.HasRequired(c => c.Department)
.WithMany(d => d.Courses)
.HasForeignKey(c => c.SomeDepartmentID);

原文链接:_http://msdn.microsoft.com/en-us/data/jj591620_

Entity Framework Core 2.0 使用代码进行自动迁移 - 晓晨Master - 博客园

Excerpt

一.前言 我们在使用EF进行开发的时候,肯定会遇到将迁移更新到生产数据库这个问题,前面写了一篇文章介绍了 “Entity Framework Core 2.0的入门使用” ,这里面介绍了使用命令生成迁移所需的SQL,然后更新到生产数据库的方法。这里还有另一种方法,就是利用EF C


一.前言

我们在使用EF进行开发的时候,肯定会遇到将迁移更新到生产数据库这个问题,前面写了一篇文章介绍了Entity Framework Core 2.0的入门使用,这里面介绍了使用命令生成迁移所需的SQL,然后更新到生产数据库的方法。这里还有另一种方法,就是利用EF Core自身所提供的方法来进行迁移。

二.API说明

这些方法都是DatabaseFacade的扩展方法,我们常使用的DbContext.Database就是DatabaseFacade类型。

  • GetMigrations 获取所有迁移
1
/// <summary> /// Gets all the migrations that are defined in the configured migrations assembly. /// </summary> public static IEnumerable<string> GetMigrations([NotNull] this DatabaseFacade databaseFacade)
  • GetPendingMigrations 获取待迁移列表
1
/// <summary> /// Gets all migrations that are defined in the assembly but haven't been applied to the target database. /// </summary> public static IEnumerable<string> GetPendingMigrations([NotNull] this DatabaseFacade databaseFacade)
  • GetAppliedMigrations 获取执行了迁移的列表
1
/// <summary> /// Gets all migrations that have been applied to the target database. /// </summary> public static IEnumerable<string> GetAppliedMigrations([NotNull] this DatabaseFacade databaseFacade)
  • Migrate 执行迁移
1
/// <summary> /// <para> /// Applies any pending migrations for the context to the database. Will create the database /// if it does not already exist. /// </para> /// <para> /// Note that this API is mutually exclusive with DbContext.Database.EnsureCreated(). EnsureCreated does not use migrations /// to create the database and therefore the database that is created cannot be later updated using migrations. /// </para> /// </summary> /// <param name="databaseFacade"> The <see cref="T:Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade" /> for the context. </param> public static void Migrate([NotNull] this DatabaseFacade databaseFacade)

三.实现自动迁移

我们可以利用上面的方法,让程序在启动的时候检查是否有待迁移,如果有那么执行迁移。这里以一个.NET Core 控制台应用程序作为示例:

1.定义一个检查迁移的方法
1
/// <summary> /// 检查迁移 /// </summary> /// <param name="db"></param> static void CheckMigrations(BloggingContext db) { Console.WriteLine("Check Migrations"); //判断是否有待迁移 if (db.Database.GetPendingMigrations().Any()) { Console.WriteLine("Migrating..."); //执行迁移 db.Database.Migrate(); Console.WriteLine("Migrated"); } Console.WriteLine("Check Migrations Coomplete!"); }

2.在程序启动时调用

1
static void Main(string[] args) { using (var db = new BloggingContext()) { //检查迁移 CheckMigrations(db); ... } }

运行:

四.制作一个单独的迁移工具

上面的方法需要我们每次在应用程序启动的时候都去检查迁移,我们也可以单独制作一个控制台程序来进行迁移的更新,这样只要在更新迁移的时候放到服务器上执行一下就行 了。

我们在实际使用中,建议将EntityFrameWork Core单独作为一个项目

代码如下:

1
static void Main(string[] args) { Console.WriteLine("Entity Framework Core Migrate Start !"); Console.WriteLine("Get Pending Migrations..."); using (var db = new BloggingContext()) { //获取所有待迁移 Console.WriteLine($"Pending Migrations:\n{string.Join('\n', db.Database.GetPendingMigrations().ToArray())}"); Console.WriteLine("Do you want to continue?(Y/N)"); if (Console.ReadLine().Trim().ToLower() == "n") { return; } Console.WriteLine("Migrating..."); try { //执行迁移 db.Database.Migrate(); } catch (Exception e) { Console.WriteLine(e); throw; } } Console.WriteLine("Entity Framework Core Migrate Complete !"); Console.WriteLine("Press any key to exit !"); Console.ReadKey(); }

执行效果:

本文Demo:https://github.com/stulzq/EntityFramework-Core-Migrator#

许可机制是ERP框架中必不可少的一部分,可以有效的保护框架资源在授权范围内应用,增加企业投资的回报。在研究了几种类型的许可机制(序列号注册码,Web服务联机验证,授权License文件)后,最后选定以Signed Xml配合RSA算法,作为许可机制的主要技术实现。

主要达到的目的如下

1  可以实现版本控制。企业版可使用所有的功能,专业版只可用部分功能,个人版免费使用,但功能集更少。

public enum Version {  Enterprise, Professional, Personal }

2  功能点的控制上,同时在线用户数量控制,帐套数量控制,硬件验证控制,试用过期控制,虚拟机控制。

  • 在线用户数量 可以控制同时在线的用户数量,超过许可数量,则无法登陆
  • 帐套数量控制 比如,只可以建立10套帐,超过此限制则无法登陆
  • 硬件验证控制 生成许可文件时,会绑定硬件信息(硬盘,CPU,内存,主板),以此硬件信息生成的许可文件,不可以在别的电脑上运行,以控制用户数量。
  • 试用过期控制 超过期限则停止进入系统,有效阻止未授权用户的继续使用,收回投资
  • 虚拟机控制  因为虚拟机中安装与还原操作系统非常容易方便,我们常以此来试用软件,当软件试用到期后还想继续用,则只需要的还原一下虚拟机中的系统,则可以继续体验。以此选项,控制软件不可以运行于虚拟机中。常见的虚拟机即VMware Workstation和Virtual PC。

以此理论,设计如下格式的License.lic文件,以作为要颁发的许可证文件。

image

在程序编写过程中,参考了CodeProject网站中的文章

Using XML Digital Signatures for Application Licensing - CodeProject
http://www.codeproject.com/Articles/4940/Using-XML-Digital-Signatures-for-Application-Licen

几乎就是对这篇文章的定制,就可以完成以上所需要达到的目的。以下分享几个遇到的实际问题,供您参考。

1 Xml序列时,元素的顺序。通过Google得知,请仔细阅读以下的几段话。

XmlSerializer takes all fields in the order that they are declared.
the order problem on the Compact Framework.Unfortunately, this is by Design.

The order of elements serialized by the NETCF xml serializer is not
guaranteed to match that of the desktop. There is nothing in the generated
schema class included in the attached project that specifies the order of
the elements.

In order to accomplish this you should add the /order option to xsd.exe and
then regenerate the schema class
(xsd.exe /order /c foo.xsd)

By doing this all the particle members will have their explicit order
identifiers and then the serializer will honor the order of the schema. The
new schema generated by with the /order switch will have the orders
property specified on the XmlElementAttribute
e.g. [System.Xml.Serialization.XmlElementAttribute(Order = 2)]

Xml序列化以元素声明的顺序,但是Compack Framework不一样,需要手动指定它们的顺序。

2 硬件信息的集成绑定

这应该是一个名值对,比如

CPU: Intel Pentium T440

Hard Driver: WD Elements

所以,需要设计一个List,或是继承于CollectionBase。

public struct HardwareInfo
{
public string HardwareId { get;set;}

public string Description { get;set;}

}

这个List要可以序列化,它要绑定到License.lic文件中。

3 类型中,有些对象不需要序列化的,要加上标签以阻止序列化。

[NonSerialized]
private string _hashValue;

4  生成公钥和私匙配对,然后放到代码中去。生成的内容如下

public static string PrivateKey
{
get
{
return “1024uCMDxXTd0bNbiAFrOYjbiGyQpqfZY2Znn70hoQZsprNoXV8tSZ6mM8VswoTNh6S+0qfYntzxpQq29mqv+8mUIuGN/30YpUq9tZFR1bIHEJnPqSRHcQa0ezimTilBN7EN7J6wnQBQqFyt3ZRnLYUsRta1Vjdn4eEc50Q4EfEOlO8=AQAB

99QWQo0ulkBCDyHwL3amXKahDSmcGa3bJHz23M++65jtxYp0LViGH+ngr5FYSxp7oAj37dKTiw4h6NO/+J6amw==

vjVO29oMfKynSHZgRIeRhcInt6ReHm19of8YIsvBVYgasg9qi0lONFUvmW51fPrXdTPWz4fHmlnv3leWN7AaPQ==tiyKHGvJthsQNC1/cHRogCzgsFtI6zt4no7ZrKFtt6PYDODk27x6A5WZW5Wc8MBL5e0RyxmC6bH+zTZypGB6Rw==Rr/bYkl75Y/u9TQa4MKwbVlnnpZD7/t4BJ63IpI5ipACpgK39bFBppOdDewZRXCkXdL3buApbY9QepqHpJUbXQ==zZu/5jmI8PSbo1e6nXfaAtzZQiSUO0q3D1Dm30w51lukRw8OlkmrMOszLF7LontM/4kLhmri2BU5yeTChppXLQ==q5JsrCqlmQRfEA4KY9Siga5u5epWA2liupOW5xw+VuGqJ/5MC2HZCTo2idUGURJvf4dHr1a9jgO60UY9bgW4kWOkLdZ3xzn0wqYyt/VCdvQE1qH/YnVEeLUZqjrbH14Zw8isR2Yxf33QCFfvHWTqIvwtm0ZdniH+cEIRgEwsPNk=“;
}
}

    public static string PublicKey
    {
        get
        {
            return "<BitStrength>1024</BitStrength><RSAKeyValue><Modulus>uCMDxXTd0bNbiAFrOYjbiGyQpqfZY2Znn70hoQZsprNoXV8tSZ6mM8VswoTNh6S+0qfYntzxpQq29mqv+8mUIuGN/30YpUq9tZFR1bIHEJnPqSRHcQa0ezimTilBN7EN7J6wnQBQqFyt3ZRnLYUsRta1Vjdn4eEc50Q4EfEOlO8=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
        }
    }

公钥只可以拿来验证许可文件,私匙可以验证,但主要的是用来生成Xml文件签名。

5 请注意License文件的最后一个节,它放的是绑定的硬件信息,这一节信息是加密的。为了可以解密,这里用RSA算法。这个过程如下,用户试用软件,申请许可文件,给用户一个EXE文件程序,用于生成hardware.id文件,然后用户将此文件发给软件公司,软件公司依据此文件,向用户发放试用许可文件。

string hardware = RSACryptionHelper.EncryptString(inputString, publicKey);

当用户将绑定有硬件信息的许可拿到别的电脑上运行时,会报异常

if ((configuration.Items.Count == 0) && (this.LicenseType == LicenseType.Enterprise))
{
throw new LicenseException(“Hardware fingerprint is missing in license file”);
}

6  许可类型

public enum LicenseType
{
Internal,
Enterprise,
Professional,
Personal
}

所有类型的license都会过期,在license中指定的ExpiredDate之后,都将无法运行。所以没有加Trial类型的许可。

我手头有个数据库工具软件,还有800多天的试用期,可还可使用2年多一点。IT这个行业既很传统,10年的技术,C#.NET WinForms技术,现在还在普及使用中,又很超前,大量的新技术,新知识注入到这个行业中。

2年的时间内可以做很多事,看很多书,走很多路,且行且看。

7  Signed Xml技术的要点主要是明文查看,但可以防止篡改。眼睁睁的看着2013-5-30号就过期了,你就是没有办法把它改成2100-5-30号。这一下子可以100年后,你改了,这企业再也不能从你这里收取费用,可怎么养活程序员呢。

但是,有两个软件可以做到一个,就是篡改系统的当前时间,然后注入到你的进程中。推荐软件RunAsDate。这软件的功能强大,可以设置当前时间,然后启动软件。如果你试用一个很好的软件,又不想付费,也找不到Cracker或是KEY的话,可以试下这个办法。

image

8   ExpiryDate是2013-5-30时,但是到了这一天,用户把时间又改回到2013-4-30号,又可以继续试用一个月。对于这个问题,也要处理。对于MIS/ERP类型的软件,也可以不用处理。对数据的主要要求之一是准确,现在是5-2号了,你把时间改回到4-2号,继续使用软件,系统的日记帐时间也是4-2号。以后查帐的时候,这是很严重的问题。5-2号的进仓单,实际对应的是系统中的4-2号的单据,很不方便与追踪问题。

如果要控制用户改时间,则需要记住用户第一次使用系统的时间,发现用户系统时间,直接报错,异常退出。

<IssuedDate>2013-04-30T10:58:52.5456701+08:00</IssuedDate>
<ExpiryDate>2013-05-30T23:59:59.997</ExpiryDate>

请到如下的网页中下载源代码,供您参考。

Using XML Digital Signatures for Application Licensing - CodeProject
http://www.codeproject.com/Articles/4940/Using-XML-Digital-Signatures-for-Application-Licen

一.什么是maven?

Maven是一个项目管理工具,它包含了一个项目对象模型 (Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。当你使用Maven的时候,你用一个明确定义的项目对象模型来描述你的项目,然后Maven可以应用横切的逻辑,这些逻辑来自一组共享的(或者自定义的)插件。

Maven 有一个生命周期,当你运行 mvn install 的时候被调用。这条命令告诉 Maven 执行一系列的有序的步骤,直到到达你指定的生命周期。遍历生命周期旅途中的一个影响就是,Maven 运行了许多默认的插件目标,这些目标完成了像编译和创建一个 JAR 文件这样的工作。

*一个jar包,会有两种下载的方式。一种是直接下载压缩包,一种是提供maven下载路径。Maven最方便的就是能帮我们下载jar包。告别传统手动导包的方式。*

二.maven仓库

maven中有中央仓库,本地仓库,私服三个概念

2.本地仓库是你在中央仓库里下载好的jia包所保存的文件夹。

3.私服相当于一个大型的本地仓库,一般在规模庞大的公司里才会有自己的私服。为什么会有私服呢? 有些公司在上班过程中某些电脑是不允许连接互联网的,只能使用自己公司的局域网,那么,这个时候你要使用maven的话就不能使用http的地址,这个时候就产生了私服。私服通过maven的索引使用公司的ip地址来使用到maven中的jar包。中国拥有最厉害的私服公司就是马云的阿里巴巴。但是阿里巴巴的私服会对外开放。

1.解压你在maven下载到的压缩包,一般把它和jdk放在一起。

2.安装maven在电脑上

鼠标右键点击计算机>点击属性>点击高级系统设置之后会弹出

然后再点击环境变量会弹出:

这个时候你就可以点击新建配置maven_home,注意maven_home的变量值是你maven的压缩包的解压地址。

                                                   

配置完maven_home之后记得还需要修改path

                                                    

在path的变量值最后加上%MAVEN_HOME%\bin;但是在配置maven_home时,你得保证你的环境变量里面存在JAVA_HOME(配置jdk)。没有配置JAVA_HOME测试时会报错。做完这些后,你就可以测试你的maven有没有安装成功。打开cmd,输入mvn -v如果你出现的是:

                                               

那么恭喜你,你安装成功了。但是这只是成功的第一步,你还需要在你的Eclipse上安装maven。

3.在Eclipse上安装maven

打开Eclipse点击window>prferences之后会弹出

                                                

                                                 

点击确定之后会出现:

                                                          

点击finish之后:

                                             

这个时候你就把maven装在你的Eclipse中了,但是我们知道,maven是一个方便我们管理jar包的工具,我们需要用到的jar包都是从maven的中央仓库里下载的,但是我们不需要每次都需要去本地仓库里下载,当我们下载过一次之后就可以在我们的本地仓库中导入jar包,那么,怎么连接本地仓库呢?

4.连接本地仓库

第一步:你首先需要找到你的maven解压文件夹,然后打开conf子文件夹,然后编辑settings.xml

 

第二步:回到eclipse中点击window再点击preferences

                                          

配置完以上步骤之后,恭喜你,可以在maven环境下建立project啦~