Entity Framework之深入分析

来源:http://www.cnblogs.com/liushaowei0/archive/2017/02/17/6410085.html
-Advertisement-
Play Games

EF雖然是一個晚生畸形的ORM框架,但功能強大又具有靈活性的,給了開發人員一定的發揮空間。因為微軟出發點總是好的,讓開發變得簡單,但實際上不是所有的事情都這麼理想。這裡順便推薦馬丁大叔的書《企業應架構模式》。 本節主要深入分析EF的分層問題,下麵是本節的已列出的要探討內容。 領域模型的概念 DbCo ...


EF雖然是一個晚生畸形的ORM框架,但功能強大又具有靈活性的,給了開發人員一定的發揮空間。因為微軟出發點總是好的,讓開發變得簡單,但實際上不是所有的事情都這麼理想。這裡順便推薦馬丁大叔的書《企業應架構模式》。

本節主要深入分析EF的分層問題,下麵是本節的已列出的要探討內容。

  • 領域模型的概念
  • DbContext與Unit of Work 的概念
  • DbContext 創建實例及線程安全問題
  • 不要隨便using或Dispose DbContext
  • DbContext的SaveChanges事務
  • Repository與UnitOfWork引入
  • DbContext T4模板的應用
  • EDM文件是放在DAL層還是Model層中?
  • EF MVC項目分層

一、領域模型的概念

領域模型:是描述業務用例實現的對象模型。它是對業務角色和業務實體之間應該如何聯繫和協作以執行業務的一種抽象。 業務對象模型從業務角色內部的觀點定義了業務用例。定義很商業,很抽象,也很理解。一個商業的概念被引入進來之後,引發很多爭議和思考。而DomainObject  在我們實際的項目又演化成大致下麵幾種

1.純事務腳本對象(只有欄位的set,get),沒有任何業務(包括沒有導航屬性),可以以理解為貧血的領域模型。

2.帶有自身業務的對象,例如驗證業務,關聯導航等。

3.對象包含量了大量的業務,而這些業務中並不是所有業務都和它相關。

尤其是第2種,界限很難劃分,怎麼判斷這個業務是自身的,還是其它的? 或者是否重用度高呢? 第一種和第三種在之前的項目都使用過,目前個人覺得EF現在走的是第2種路線,EF在生成Model模型後,依然可以對模型進行業務修改。我們也不必在這樣上面糾結太多,項目怎麼做方便就怎麼去實現。比如純凈的POCO我可以當DTO或VO使用;而第3種情況,我們在微軟的DataSet時,也是大量使用的。想詳細瞭解這段的可以參照這篇討論

二、DbContext與Unit of Work 的概念

在馬丁大叔中書看我們可以準看到Unit of Work 的定義:維護受業務事務影響的對象列表,並協調變化的寫入和併發問題的解決。即管理對象的CRUD操作,以及相應的事務與併發問題等。Unit of Work的是用來解決領域模型存儲和變更工作,而這些數據層業務並不屬於領域模型本身具有的。而DbContext其實就是一個Unit of work ,只是如果直接使用這個DbContext 的話,那DbContext所有的業務都是直接暴露的,當然這是看是否項目需要了。可以看出微軟的EF DbContext借用了Unit of work的思想。

三、DbContext 創建實例及線程安全問題

1. DbContext不適合創建成單例模式,例如A對象正在編輯,B對象編輯完了提交,導致正在編輯的A對象也被提交了,但是A的改可能要取消的,但是最終都被提交到資料庫中了。

2. 如果DbContext創建過多的實例,就要控制好併發的問題,因為不同實例的DbContext可能會對同一條記錄進行修改。

3. DbContext線程安全問題,同一實例的DbContext被不同線程調用會引發第一條場景的情況。不同線程使用不同實例的DbContext時又會引發第二種場景的情況。

第一種情況很難控制,而第二種情況可以採用樂觀併發鎖來解決,其次就是儘量避免對一記錄的寫操作。

四、不要隨便using或Dispose DbContext

我們先來看一段代碼

View Code
            BlogCategory cate =null;
using (DemoDBEntities context =new DemoDBEntities())
{
//context.Configuration.LazyLoadingEnabled = false;
DbSet<BlogCategory>set= context.Set<BlogCategory>();
cate
=set.Find(2);
}

//肯定會出錯 因為DbContext被釋放了 無法延遲載入對象
BlogArticle blog = cate.BlogArticle.First();

當我們在使用延遲載入的時候,如果使用using或dispose 釋放掉DbContext後,就無法延遲載入導航屬性。為什麼?我們來看一下DbContext是如何載入對象以及導航屬性的。

將上面的代碼修改一下

View Code
staticvoid Main(string[] args)
{
BlogCategory cate
=null;
using (DemoDBEntities context =new DemoDBEntities())
{
//context.Configuration.LazyLoadingEnabled = false;
DbSet<BlogCategory>set= context.Set<BlogCategory>();
cate
=set.Find(2);

//肯定會出錯 因為DbContext被釋放了 無法延遲載入對象
BlogArticle blog = cate.BlogArticle.First();
}

Console.ReadLine();
}

我們打開SQL Server Profiler 來監視一上面的代碼執行情況

 

可以看如果DbContext如果在第一次讀取BlogCategory被釋放後,那在載入導航屬性的時候肯定不會執行成功。

另外一點:為什麼很多人一定要using 或dispose掉DbContext ?

是擔心資料庫連接沒有釋放?還是擔心DbContext占用過多資源呢?

首先擔心資料庫連接沒有釋放肯定是多餘的,因為DbContext在SaveChanges完成後會釋放掉打開的資料庫連接,我們來反編譯一下SaveChages的源碼看看

View Code
publicvirtualint SaveChanges(SaveOptions options)
{
this.OnSavingChanges();
if ((SaveOptions.DetectChangesBeforeSave & options) != SaveOptions.None)
{
this.ObjectStateManager.DetectChanges();
}
if (this.ObjectStateManager.SomeEntryWithConceptualNullExists())
{
thrownew InvalidOperationException(Strings.ObjectContext_CommitWithConceptualNull);
}
bool flag =false;
int objectStateEntriesCount =this.ObjectStateManager.GetObjectStateEntriesCount(EntityState.Modified | EntityState.Deleted | EntityState.Added);
using (new EntityBid.ScopeAuto("<dobj.ObjectContext.SaveChanges|API> %d#, affectingEntries=%d", this.ObjectID, objectStateEntriesCount))
{
EntityConnection connection
= (EntityConnection) this.Connection;
if (0>= objectStateEntriesCount)
{
return objectStateEntriesCount;
}
if (this._adapter ==null)
{
IServiceProvider providerFactory
= connection.ProviderFactory as IServiceProvider;
if (providerFactory !=null)
{
this._adapter = providerFactory.GetService(typeof(IEntityAdapter)) as IEntityAdapter;
}
if (this._adapter ==null)
{
throw EntityUtil.InvalidDataAdapter();
}
}
this._adapter.AcceptChangesDuringUpdate =false;
this._adapter.Connection = connection;
this._adapter.CommandTimeout =this.CommandTimeout;
try
{
this.EnsureConnection();
flag
=true;
Transaction current
= Transaction.Current;
bool flag2 =false;
if (connection.CurrentTransaction ==null)
{
flag2
=null==this._lastTransaction;
}
using (DbTransaction transaction =null)
{
if (flag2)
{
transaction
= connection.BeginTransaction();
}
objectStateEntriesCount
=this._adapter.Update(this.ObjectStateManager);
if (transaction !=null)
{
transaction.Commit();
}
}
}
finally
{
if (flag)
{
this.ReleaseConnection();
}
}
if ((SaveOptions.AcceptAllChangesAfterSave & options) == SaveOptions.None)
{
return objectStateEntriesCount;
}
try
{
this.AcceptAllChanges();
}
catch (Exception exception)
{
if (EntityUtil.IsCatchableExceptionType(exception))
{
throw EntityUtil.AcceptAllChangesFailure(exception);
}
throw;
}
}
return objectStateEntriesCount;
}

可以看到DbContext 每次打開  EntityConnection  最後都會 finally 時 通過this.ReleaseConnection() 釋放掉連接,所以這個擔心是多餘的。

其次DbContext 是否占用過多的資源呢?DbContext確實占用了資源,主要體現在DbContext的Local屬性上,每一次的增刪改查,Loacl都會從資料庫中載入數據,而這些數據在SaveChanges之後並沒有釋放掉。因此釋放DbContext 是需要的,但是這樣又會影響到延遲載入。這樣的話,我們可以通過重載SaveChanges,在SaveChanges之後清除掉Local中的數據。但是這樣做為什麼有問題,我也不知道,有待考證。上一節中有介紹重載SaveChanges 清除Local 數據阻止查詢數據更新。 

五、DbContext的SaveChanges自帶事務與分散式事務

通過反編譯可以看到單實例DbContext的SaveChanges方式預設開啟了事務,當同時更新多條記錄時,有一條失敗就會RollBack。模擬測試代碼

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EF.Model;
using EF.DAL;
using System.Data.Entity;
using System.Collections;
using System.Transactions;
namespace EF.Demo
{
class Program
{
staticvoid Main(string[] args)
{
BlogCategory cate
=null;
DemoDBEntities context
=new DemoDBEntities();

//DemoDBEntities context2 = new DemoDBEntities();
try
{
//using (TransactionScope scope = new TransactionScope())
//{
//context.Configuration.LazyLoadingEnabled = false;
DbSet<BlogCategory>set= context.Set<BlogCategory>();

cate
=new BlogCategory();
cate.CateName
="2010-7";
cate.CreateTime
= DateTime.Now;

cate.BlogArticle.Add(
new BlogArticle() { Title ="2011-7-15" });
set.Add(cate);

//由於沒設置Title欄位,並且CreateTime欄位不能為空,故會引發異常
context.Set<BlogArticle>().Add(new BlogArticle { BlogCategory_CateID =2 });
int a = context.SaveChanges();

// context2.Set<BlogArticle>().Add(new BlogArticle { BlogCategory_CateID = 2 });
// int b = context2.SaveChanges();

// scope.Complete();
//}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}


Console.ReadLine();
}
}
}

通過SQL SERVER Profile 監視到沒有一句SQL語句被執行,SaveChanges事務是預執新所有操作成功後才會更新到資料庫中。

我們再來測試一下分散式事務,創建的Context2用於模擬代表其它資料庫

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EF.Model;
using EF.DAL;
using System.Data.Entity;
using System.Collections;
using System.Transactions;
namespace EF.Demo
{
class Program
{
staticvoid Main(string[] args)
{
BlogCategory cate
=null;
DemoDBEntities context
=new DemoDBEntities();

DemoDBEntities context2
=new DemoDBEntities();
try
{
using (TransactionScope scope =new TransactionScope())
{
//context.Configuration.LazyLoadingEnabled = false;
DbSet<BlogCategory>set= context.Set<BlogCategory>();

cate
=new BlogCategory();
cate.CateName
="2010-7";
cate.CreateTime
= DateTime.Now;

cate.BlogArticle.Add(
new BlogArticle() { Title ="2011-7-15" });
set.Add(cate);
//實例1 對資料庫執行提交
int a = context.SaveChanges();

//實例2 模擬其它資料庫提交 時間欄位為空,無法更新成功
context2.Set<BlogArticle>().Add(new BlogArticle { Title="2011-7-16", BlogCategory_CateID =2 });
int b = context2.SaveChanges();

scope.Complete();
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}


Console.ReadLine();
}
}
}

通過SQL SERVER Profile 監視,雖然context實際執行了兩條SQL記錄,但是context2的SQL沒有執行成功,導致事務回滾,所有操作都被沒有執行成功。 

六、Repository與UnitOfWork引入

Repository是什麼? 馬丁大叔的書上同樣也有解釋:它是銜接數據映射層和域之間的一個紐帶,作用相當於一個在記憶體中的域對象映射集合,它分離了領域對象和資料庫訪問代碼的細節。Repository受DomainObject驅動,Repository用於實現不屬於DomainObject的自身相關的,但又受DomainObject約束的業務。如CRUD操作就不是領域模型要關註的業務,但是領域模型最終要映射為數據關係保存到資料庫中。一個領域模型要有對應的Repository來處理與數據層銜接過程。但不是所有的DomainObject對Repository約束是相同的,可能這個領域對象沒有對應Repository刪除操作,而別外一個卻有,所以我們經常使用的泛型Repository<T> 是不合適的。但是為了代碼簡潔重用,大家根據實際情況還是使用了簡潔的IRepository<T>介面,就像我們有時為了簡單直接把POCO當DTO或VO使用了。如果不引入Repository,我覺得沒有必要實現DAL層,因為DbContext本身就是DAL層,然後只要為DbContext定義好接IDAL介面從而必免與BLL層的耦合。從這裡就可以看出Repository與DAL的區別,一個受域業務驅動出現的,一個是出於解除耦合出現的。

UnitOfWork 工作單元,前面已經介紹過。為了減少業務層頻繁調用DbContext的SaveChanges同步資料庫操作(將多個對象的更新一次提交,減少與資料庫交互過程),又要保證DbContext對業務層封閉,所以我們要增加一個對業務層開放的介面。想一想如果把SaveChanges的方法下放到每個Repository中或者DAL中,那業務層在協調多個Repository事務操作時,就會頻繁的寫資料庫。而分離了Repository中的所有SaveChanges (或者撤銷以及完成單元工作後銷毀等操作)後,並通過介面在業務層統一調用,這樣既大大提高了效率,也體現了一個完整的單元工作業務。

七、DbContext T4模板的應用

在Model First中,我們藉助於EDMX 和T4模板完成了DbContext和Model的初步設計。但是微軟提供的這些模板不能滿足用戶的所有需求,這個時候我就要修改T4 來生成我們想要的代碼。

T4模板應用非常廣泛,很多ORM工具的模板也在使用的T4模板,T4也可以生成HTML,JS等多種語言。T4模板支持多種語言書寫,可讀性很強,也容易上手。

DbContext模板 一共分為兩個 DemoDB.DbContext.tt (unit of work)和DemoDb.tt (model) 。前一節我們介紹瞭如何修改DemoDb.tt 模板 使我們POCO模型繼承POCOEntity,這一節再修改一下DemoDb.DbContext.tt模板 使其繼承IUnitOfWork介面。 

首先我們在Model層中增加IUnitOfWork介面如下

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EF.Model
{
publicinterface IUnitOfWork
{
//事務提交
int Save();
}
}

我們再修改DemoDb.DbContext.tt模板

View Code
<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#><#@
output extension
=".cs"#><#

var loader
=new MetadataLoader(this);
var region
=new CodeRegion(this);
//---------------------------------------------------這裡導入了DemoDB.edmx映射文件---------------add by mecity
var inputFile =@"DemoDB.edmx";
//---------------------------------------------------映射文件轉為集合方便模板篇歷生成代碼--------add by mecity
var ItemCollection = loader.CreateEdmItemCollection(inputFile);

Code
=new CodeGenerationTools(this);
EFTools
=new MetadataTools(this);
ObjectNamespace
= Code.VsNamespaceSuggestion();
ModelNamespace
= loader.GetModelNamespace(inputFile);

EntityContainer container
= ItemCollection.GetItems<EntityContainer>().FirstOrDefault();
if (container ==null)
{
returnstring.Empty;
}
#
>
//------------------------------------------------------------------------------
// <auto-generated>
// <#=GetResourceString("Template_GeneratedCodeCommentLine1")#>
//
// <#=GetResourceString("Template_GeneratedCodeCommentLine2")#>
// <#=GetResourceString("Template_GeneratedCodeCommentLine3")#>
// </auto-generated>
//------------------------------------------------------------------------------

<#

if (!String.IsNullOrEmpty(ObjectNamespace))
{
#
>
namespace<#=Code.EscapeNamespace(ObjectNamespace)#>
{
<#
PushIndent(CodeRegion.GetIndent(
1));
}

#
>
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
//---------------------------------------------------這加加入對EF.Model命名空間的引用---------------add by mecity
using EF.Model;
<#
if (container.FunctionImports.Any())
{
#
>
using System.Data.Objects;
<#
}
#
>

//---------------------------------------------------這裡加入對IUnitOfWork介面繼承---------------add by mecity
<#=Accessibility.ForType(container)#>partialclass<#=Code.Escape(container)#> : DbContext,IUnitOfWork
{
public<#=Code.Escape(container)#>()
:
base("name=<#=container.Name#>")
{
<#
WriteLazyLoadingEnabled(container);
#
>
}

protectedoverridevoid OnModelCreating(DbModelBuilder modelBuilder)
{
thrownew UnintentionalCodeFirstException();
}

//---------------------------------------------------這裡加入對IUnitOfWork介面方法的實現---------------add by mecity
publicint Save()
{
returnbase.SaveChanges();
}

註意T4模板中加了註釋的地方,保存模板後,就會重新創建DemoDBEntities,看一下模板修改後生成後的代碼

View Code
//------------------------------------------------------------------------------
// <auto-generated>
// 此代碼是根據模板生成的。
//
// 手動更改此文件可能會導致應用程式中發生異常行為。
// 如果重新生成代碼,則將覆蓋對此文件的手動更改。
// </auto-generated>
//------------------------------------------------------------------------------

namespace EF.DAL
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
//---------------------------------------------------這加加入對EF.Model命名空間的引用---------------add by mecity
using EF.Model;

//---------------------------------------------------這裡加入對IUnitOfWork介面繼承---------------add by mecity
publicpartialclass DemoDBEntities : DbContext,IUnitOfWork
{
public DemoDBEntities()
:
base("name=DemoDBEntities")
{
}

protectedoverridevoid OnModelCreating(DbModelBuilder modelBuilder)
{
thrownew UnintentionalCodeFirstException();
}

//---------------------------------------------------這裡加入對IUnitOfWork介面方法的實現---------------add by mecity
publicint Save()
{
returnbase.SaveChanges();
}

public DbSet<BlogArticle> BlogArticle { get; set; }
public DbSet<BlogCategory> BlogCategory { get; set; }
public DbSet<BlogComment> BlogComment { get; set; }
public DbSet<BlogDigg> BlogDigg { get; set; }
public DbSet<BlogTag> BlogTag { get; set; }
public DbSet<BlogMaster> BlogMaster { get; set; }
}
}

八、EDM文件是放在DAL層還是Model層中?

記得我第一篇EF介紹中將EDMX文件和Model放在一起,這樣做有一定風險,按照領域模型的概念,Model中這些業務對象被修改的可能性非常高,並且每個業務對象的修改的業務都可能不同,因此修改DemoDB.tt模板滿足所有對象是不實現的, 並且意外保存EDMX文件時,也會導致Model手動修改的內容丟失。因此EDMX不適合和Model放在一起,最好移至到DAL層或Repository層。DAL中的DemoDb.DbContext.tt模板生成代碼是相對固定的(只有一個DemoDBEntities類),因此對DemoDb.DbContext.tt模板的修改基本可以滿足要求。見上節T4應用。我們可以先在DAL中的EDMX完成POCO對象的初步生成與映射關係工作後,再移至到Model中處理。

九、EF MVC項目分層 

就目前CodePlex上的微軟項目NorthwindPoco/Oxite/Oxite2)以及其它開源的.net mvc EF項目分層來看,大致結構如下

View   視圖

Controller 控制器

IService  Controller調用具體業務的介面

Service   IService的具體實現 ,利用IOC註入到Controller

Repository 是IRepository 的具體實現,利用IOC註入到Service

Model+IRepository  因為IRepository介面對應的是DoaminModel約束業務,並且都是直接開放給Service 調用的,所以放在一個類庫下也容易理解,當然分開也無影響。

VO/DTO  ViewObject與DTO 傳輸對象類庫

當然這隻是參考,怎麼合理分層還是依項目需求,項目進度,資源情況以及後期維護等情況而定。

 

參考頁面:

http://www.yuanjiaocheng.net/entity/database-first.html

http://www.yuanjiaocheng.net/entity/choose-development-approach.html

http://www.yuanjiaocheng.net/entity/query-with-edm.html

http://www.yuanjiaocheng.net/entity/linq-to-entities-projection.html

http://www.yuanjiaocheng.net/entity/dbset-class.html

it學習資料


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 初步應用vs2012這軟體,語言選擇c# , 框架選擇4(不要選擇最前和太後的框架)然後改個名字和保存路徑點確定就行了。 在main函數中寫代碼,大括弧裡面。 首先是最基本的輸入與輸出: Console.WriteLine(");//直接會輸出引號裡面的內容(如果直接寫入Write的話就不會換行。) ...
  • 電子面單開發流程 服務程式 生成單號,改變三個表: 抓類型表,抓取未處理的充值記錄->根據類型取面單類型表的最大單號(判斷是否在起始和結束之間,並設置郵件或者簡訊預警 )->根據單號規則和充值的數量生成單號明細入單號明細表,改變充值記錄的處理狀態 改變面單類型最大單號(事務提交)。 ...
  • 經過本周的努力,昨晚終於完成OSS.Social微信項目的標準庫支持,當前項目你已經可以同時在.net framework和.net core 中進行調用,調用方法也發生了部分變化,這裡我簡單分享下,主要包含下邊幾個部分: · 移植後的變化 · 和OSS.Common,OSS.Http關係 · 非同步 ...
  • #define aaa //放在代碼最前面 int a = 1; a = a + 1; #if !aaa {a = a + 1;}#elif !aaaaa {a=a+11;}#endif Console.WriteLine(a); Console.ReadKey(); 據說與版本有關 ,#undef ...
  • 為什麼叫T4?因為簡寫為4個T。 T4(Text Template Transformation Toolkit)是微軟官方在VisualStudio 2008中開始使用的代碼生成引擎。在 Visual Studio 中,“T4 文本模板”是由一些文本塊和控制邏輯組成的混合模板,它可以生成文本文件。 ...
  • 在大型網站系統中,為了提高系統訪問性能,往往會把一些不經常變得內容發佈成靜態頁,比如商城的產品詳情頁,新聞詳情頁,這些信息一旦發佈後,變化的頻率不會很高,如果還採用動態輸出的方式進行處理的話,肯定會給伺服器造成很大的資源浪費。但是我們又不能針對這些內容都獨立製作靜態頁,所以我們可以在系統中利用偽靜態 ...
  • Static Using static using聲明允許直接調用靜態方法而不需要指定類名: C# 5 C# 6 Expression-Bodied Methods 使用expression-bodied methods,一個只有一句statement的函數可以使用lambda寫法。 C# 5 C# ...
  • 5.5 HTTP Cookies in ASP.NET Web API 5.5 ASP.NET Web API中的HTTP Cookie 本文引自:http://www.asp.net/web-api/overview/working-with-http/http-cookies By Mike W ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...