之前简单介绍了XLinq的一些功能,有很多功能都没有提到,现在给XLinq加了一些功能,这次把所有功能都介绍一遍。
设计目标
-
易用性
在使用一个框架的时候
应该没几个人会喜欢写一大堆的配置文件吧
也应该没几个人会喜欢为了完成一个小功能却需要写一大堆代码
这是XLinq开发的首要目标之一,就是尽可能提高易用性
最小配置的情况下仅需要一句连接字符串的配置就可以使用
默认支持的是Sql Server 2008 R2数据库,理论上说也大部分支持了sql server系的其他数据库
-
高性能
目前针对查询时的DataReader转List和批量插入有针对性优化,其他的不太重要的例如批量更新这个并没有太多优化
-
易于调试
出了问题能够以最快速度让使用者定位到问题,其实这些是细节问题
-
支持LINQ
这个其实是跟易用性挂勾的,园子里面有大神写的ORM,声称"无Linq",其实我也不太想用Linq,因为解析Linq实在太难,坑太多,但又找不到另一种比Linq更让我满意的方案。然后我去看了他的ORM的使用方法,发现一旦复杂起来不见得会比Linq更清晰,并且实体类的设计实在是···
Linq实现复杂的语法确实比较蛋疼,sql语句里面直接来几个case when,linq就得写晕过去,至少解析的时候会晕过去。
但在我看来,既然linq语句都比较复杂了,那肯定是有很多的逻辑在里面,就像上面说的case when,那么为什么不把这些逻辑分拆成好几个linq去执行呢?或者干脆点写存储过程里面。
- 多数据库支持
使用方法
- 建立控制台应用程序,暂时只支持.net 4.5.2版本
-
安装xlinq nuget包
-
建立数据库XLinq及相关表
-
添加配置文件
- <connectionStrings>
- <add name="test" connectionString="Data Source=(local);;initial catalog=XLinq;User Id=xinchen;Password=123456"/>
- </connectionStrings>
-
建立TestDataContext类和User实体类
- public class TestDataContext:DataContext
- {
- public TestDataContext():base("test")
- {
- }
- }
- public class User
- {
- public int Id { get; set; }
- public string Username { get; set; }
- }
-
开始使用
- TestDataContext db = new TestDataContext();
- db.Set<User>().ToList();
查询
-
单表查询
Lambda版:
- db.Set<User>().Where(x => x.Username == "xxxx").ToList();
LINQ版:
- (from user in db.Set<User>() where user.Username == "xxxx" select user).ToList();
LINQ版看起来明显感觉比较麻烦,所以在简单查询的时候我更倾向于Lambda表达式
上面的语句生成的代码是一致的
- [Users1].[Id] [Id]
- ,[Users1].[Username] [Username]
-
多表查询
建立实体类的过程不再说
两个表连接查询,因为用Lambda实现比较复杂,所以后面都不再用Lambda实现
- var query = from user in db.Set<User>()
- join userRole in db.Set<UserRole>() on user.Id equals userRole.UserId
- where user.Id == 1 && userRole.RoleId == 1
- select new
- {
- user.Id,
- userRole.RoleId,
- user.Username
- };
生成的语句
五个表连接查询
- var query = from user in db.Set<User>()
- join userRole in db.Set<UserRole>() on user.Id equals userRole.UserId
- join rolePrivilege in db.Set<RolePrivilege>() on userRole.RoleId equals rolePrivilege.RoleId
- join priviege in db.Set<Privilege>() on rolePrivilege.PrivilegeId equals priviege.Id
- join role in db.Set<Role>() on userRole.RoleId equals role.Id
- where user.Id == 1 && userRole.RoleId == 1
- select new
- {
- user.Id,
- userRole.RoleId,
- user.Username,
- PrivilegeName = priviege.Name,
- RoleName = role.Name
- };
生成的语句
-
左连接查询
因为linq本身的左连接写法比较蛋疼,所以xlinq也没有办法,后面会想办法简化
- var query = from user in db.Set<User>()
- join userRole in db.Set<UserRole>() on user.Id equals userRole.UserId into urs
- from ur in urs.DefaultIfEmpty()
- where user.Id == 1 && ur.RoleId == 1
- select new
- {
- user.Id,
- ur.RoleId,
- user.Username
- };
生成的语句
-
延迟加载
- var query = db.Set<User>().Where(x => x.Username == "xxxxx").Select(x => x.Id);
- foreach (var item in query)
- {
- }
-
自连接查询
- var query = from user1 in db.Set<User>()
- join user2 in db.Set<User>() on user1.Id equals user2.Id
- select user1;
生成的语句
-
SQL语句查询
- db.SqlQuery<User>("select * from dbo.users", new Dictionary<string, object>());
-
分页查询
- var page = 1;
- var pageSize = 10;
- var query = (from user in db.Set<User>()
- join userRole in db.Set<UserRole>() on user.Id equals userRole.UserId
- join rolePrivilege in db.Set<RolePrivilege>() on userRole.RoleId equals rolePrivilege.RoleId
- join priviege in db.Set<Privilege>() on rolePrivilege.PrivilegeId equals priviege.Id
- join role in db.Set<Role>() on userRole.RoleId equals role.Id
- where user.Id == 1 && userRole.RoleId == 1
- orderby user.Id descending
- select new
- {
- user.Id,
- userRole.RoleId,
- user.Username,
- PrivilegeName = priviege.Name,
- RoleName = role.Name
- }).Skip((page - 1) * pageSize).Take(pageSize);
生成的语句
-
动态查询
- IQueryable<User> query = db.Set<User>();
- var filters = new List<SqlFilter>();
- filters.Add(SqlFilter.Create("Id", Operation.Equal, 1));
- filters.Add(SqlFilter.Create("Username", Operation.Like, "aaa"));
- query = query.Where(filters);
生成的语句
- [Users1].[Id] [Id]
- ,[Users1].[Username] [Username]
-
取日期查询
这个功能主要针对EF中无法直接取日期的问题
- var query = db.Set<User>().Where(x => x.LoginTime.Date == DateTime.Now.Date);
- [Users1].[Id] [Id]
- ,[Users1].[Username] [Username]
- ,[Users1].[LoginTime] [LoginTime]
-
计算天数查询
- var query = db.Set<User>().Where(x => (x.LoginTime - DateTime.Now).TotalDays <= 7);
生成的语句
- [Users1].[Id] [Id]
- ,[Users1].[Username] [Username]
- ,[Users1].[LoginTime] [LoginTime]
修改
-
先查询后修改(注意只有十条数据以内才会支持修改)
- var query = db.Set<User>().FirstOrDefault();
- query.Username = "xxxxxxx";
- db.SaveChanges();
-
直接修改
- db.Set<User>().Where(x => x.Id == 1).Update(x => new User
- {
- Username = "xxxxxxxxxxxxx"
- });
Update子句必须采用这写法才会有效
删除
-
先查询后删除
- var query = db.Set<User>().FirstOrDefault();
- db.Set<User>().Remove(query);
- db.SaveChanges();
-
直接删除
- db.Set<User>().Where(x => x.Id == 1).Delete();
事务
-
普通事务
- using (var scope = new TransactionScope())
- {
- db.Set<User>().Where(x => x.Id == 1).Delete();
- scope.Complete();
- }
-
嵌套事务
- using (var scope = new TransactionScope())
- {
- db.Set<User>().Where(x => x.Id == 1).Delete();
- using (var s1 = new TransactionScope())
- {
- db.Set<Privilege>().Where(x => x.Id == 1).Delete();
- s1.Complete();
- }
- scope.Complete();
- }
调试支持
在调试的时候可直接看到SQL语句
多数据库支持
通过配置文件实现多数据库支持
- <configSections>
- <section name="xlinq" type="Xinchen.XLinq.ConfigSection,Xinchen.XLinq"/>
- </configSections>
- <xlinq connectionStringName="test" dataBase="SQLite"></xlinq>
链式Api
有没有园友注意到,我上面用的User实体并没有进行任何特殊处理,但实际翻译出来的语句是识别的Users表
这其实是"抄"的EF的约定大于配置的原则,默认情况下,实体名的复数就是表名,实体中的Id识别为主键并自动增长
如果需要自定义这些规则,则需要使用特性或链式API,我现在更喜欢链式API
- public class TestDataContext:DataContext
- {
- public TestDataContext():base("test")
- {
- }
- protected override void ConfigurationModel(Xinchen.XLinq.Configuation.EntityConfigurationManager entityConfiguration)
- {
- entityConfiguration.Entity<User>().TableName("Users").Key(x => x.Id, Xinchen.DbUtils.DataAnnotations.DataSourceTypes.AutoIncreament);
- }
- }
重写DataContext的ConfigurationModel方法即可使用链式API对实体相关信息进行配置,上面是指定了User实体的表名、主键及主键的数据来源为自动增长
创建表
若要创建表,则几乎必须使用链式API对实体进行配置之后才能进行自动创建
另外这个功能有一定的限制,默认情况下不会启用该功能,若启用了也只有在数据库中一个表都没有的情况下才会自动创建表
这样做是因为真正的数据库运行环境其实很可能压根不允许自动创建表,然后就是下面这个样子,这样一来这个功能其实并没有太大用,只有在第一次初始化数据库的时候才会用。
当然也可以始终自动创建表,这个需要稍微配置一下,当allwayAutoCreateTables为true时并且autoCreateTables为true时,将始终自动创建表
下面演示创建一个Test表
DataContext中的配置
- public class TestDataContext : DataContext
- {
- public TestDataContext()
- : base("test")
- {
- }
- protected override void ConfigurationModel(Xinchen.XLinq.Configuation.EntityConfigurationManager entityConfiguration)
- {
- var testEntity = entityConfiguration.Entity<Test>().TableName("TestTables").Key(x => x.Id, Xinchen.DbUtils.DataAnnotations.DataSourceTypes.AutoIncreament);
- testEntity.Property(x => x.Id).Name("TId");
- testEntity.Property(x => x.Name).MaxLength(100);
- }
- }
创建的表
自定义Provider(有这功能,但没有测试)
性能
-
查询
- class Program
- {
- static int count = 1;
- static int rowCount = 1000000;
- static void Main(string[] args)
- {
- TestDataContext xlinqDb = new TestDataContext();
- TestDbContext efDb = GetEF(null);
- var userDB = xlinqDb.Set<User>();
- var userRoleDb = xlinqDb.Set<UserRole>();
- var efUserDb = efDb.Users;
- var efUserRoleDb = efDb.UserRoles;
- ExecuteTimer("XLinq使用Linq", () =>
- {
- for (int i = 0; i < count; i++)
- {
- userDB.Take(rowCount).ToList();
- }
- });
- ExecuteTimer("XLinq使用SQL", () =>
- {
- var sql = "select top " + rowCount + " * from dbo.users";
- var dict = new Dictionary<string, object>();
- for (int i = 0; i < count; i++)
- {
- xlinqDb.SqlQuery<User>(sql, dict);
- }
- });
- ExecuteTimer("XLinq多表", () =>
- {
- for (int i = 0; i < count; i++)
- {
- (from user in userDB
- join userRole in userRoleDb on user.Id equals userRole.UserId into us
- from u in us.DefaultIfEmpty()
- select user).Take(rowCount).ToList();
- }
- });
- efDb = GetEF(efDb);
- efUserDb = efDb.Users;
- efUserRoleDb = efDb.UserRoles;
- ExecuteTimer("EF使用LINQ", () =>
- {
- for (int i = 0; i < count; i++)
- {
- efUserDb.Take(rowCount).ToList();
- }
- });
- efDb = GetEF(efDb);
- efUserDb = efDb.Users;
- efUserRoleDb = efDb.UserRoles;
- ExecuteTimer("EF使用SQL", () =>
- {
- var sql = "select top " + rowCount + " * from dbo.users";
- for (int i = 0; i < count; i++)
- {
- efDb.Database.SqlQuery<User>(sql).ToList();
- }
- });
- efDb = GetEF(efDb);
- efUserDb = efDb.Users;
- efUserRoleDb = efDb.UserRoles;
- ExecuteTimer("EF多表", () =>
- {
- for (int i = 0; i < count; i++)
- {
- (from user in efUserDb
- join userRole in efUserRoleDb on user.Id equals userRole.UserId into us
- from u in us.DefaultIfEmpty()
- select user).Take(rowCount).ToList();
- }
- });
- SqlConnection conn = new SqlConnection();
- conn.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings[1].ConnectionString;
- ExecuteTimer("Dapper", () =>
- {
- var sql = "select top " + rowCount + " * from dbo.users";
- for (int i = 0; i < count; i++)
- {
- conn.Query<User>(sql);
- }
- });
- Console.ReadKey();
- }
- static void ExecuteTimer(string name, Action action)
- {
- var watcher = Stopwatch.StartNew();
- action();
- watcher.Stop();
- Console.WriteLine(string.Format("{0}查询{1}数据{2}次", name, rowCount, count).PadRight(30) + ":" + watcher.Elapsed);
- }
- static TestDbContext GetEF(TestDbContext ef)
- {
- if (ef != null)
- {
- ef.Dispose();
- }
- TestDbContext efDb = new TestDbContext();
- efDb.Users.Where(x => false).ToList();
- efDb.Configuration.AutoDetectChangesEnabled = false;
- efDb.Configuration.ProxyCreationEnabled = false;
- efDb.Configuration.ValidateOnSaveEnabled = false;
- return efDb;
- }
- public class TestDbContext : DbContext
- {
- public TestDbContext()
- : base("name=test")
- {
- }
- public System.Data.Entity.DbSet<User> Users { get; set; }
- public System.Data.Entity.DbSet<UserRole> UserRoles { get; set; }
- }
- }
测试结果
查询一次的情况下EF慢的妥妥的,XLinq微弱优势
查询N次的情况下EF这···还有人说EF慢?不过大概是因为缓存的原因,但最后一张图看着又不太像,或许还跟GC什么的关吧,所以还有人说EF慢么··
或者是我测试代码有问题?
-
插入
- class Program
- {
- static int count = 10;
- static int rowCount = 1000;
- static void Main(string[] args)
- {
- TestDataContext xlinqDb = new TestDataContext();
- TestDbContext efDb = GetEF(null);
- var userDB = xlinqDb.Set<User>();
- var userRoleDb = xlinqDb.Set<UserRole>();
- var efUserDb = efDb.Users;
- var efUserRoleDb = efDb.UserRoles;
- ExecuteInsertTimer("XLinq", () =>
- {
- for (int i = 0; i < rowCount; i++)
- {
- userDB.Add(new User()
- {
- C1 = "x",
- C2 = "x",
- C3 = "x",
- C4 = "x",
- C5 = "x",
- C6 = "x",
- C7 = "x",
- C8 = "x",
- C9 = "x",
- C10 = "x",
- Username = "xxxx"
- });
- }
- xlinqDb.SaveChanges();
- });
- ExecuteInsertTimer("EF", () =>
- {
- for (int i = 0; i < rowCount; i++)
- {
- efUserDb.Add(new User()
- {
- C1 = "x",
- C2 = "x",
- C3 = "x",
- C4 = "x",
- C5 = "x",
- C6 = "x",
- C7 = "x",
- C8 = "x",
- C9 = "x",
- C10 = "x",
- Username = "xxxx"
- });
- }
- efDb.SaveChanges();
- });
- Console.ReadKey();
- }
- static void ExecuteTimer(string name, Action action)
- {
- var watcher = Stopwatch.StartNew();
- action();
- watcher.Stop();
- Console.WriteLine(string.Format("{0}查询{1}数据{2}次", name, rowCount, count).PadRight(30) + ":" + watcher.Elapsed);
- }
- static void ExecuteInsertTimer(string name, Action action)
- {
- var watcher = Stopwatch.StartNew();
- action();
- watcher.Stop();
- Console.WriteLine(string.Format("{0}插入{1}条数据", name, rowCount).PadRight(30) + ":" + watcher.Elapsed);
- }
- static TestDbContext GetEF(TestDbContext ef)
- {
- if (ef != null)
- {
- ef.Dispose();
- }
- TestDbContext efDb = new TestDbContext();
- efDb.Users.Where(x => false).ToList();
- efDb.Configuration.AutoDetectChangesEnabled = false;
- efDb.Configuration.ProxyCreationEnabled = false;
- efDb.Configuration.ValidateOnSaveEnabled = false;
- return efDb;
- }
- public class TestDbContext : DbContext
- {
- public TestDbContext()
- : base("name=test")
- {
- }
- public System.Data.Entity.DbSet<User> Users { get; set; }
- public System.Data.Entity.DbSet<UserRole> UserRoles { get; set; }
- }
- }
测试结果