之前簡單介紹了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
-
};
生成的語句
-
[user].[Id] [Id]
-
,[userRole].[RoleId] [RoleId]
-
,[user].[Username] [Username]
-
,[priviege].[Name] [PrivilegeName]
-
,[role].[Name] [RoleName]
-
-
ON [userRole].[RoleId] = [rolePrivilege].[RoleId]
-
-
INNER JOIN [Privileges] [priviege]
-
ON [rolePrivilege].[PrivilegeId] = [priviege].[Id]
-
-
ON [userRole].[RoleId] = [role].[Id]
-
-
-
左連接查詢
因為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; }
-
}
-
}
測試結果
-