1. 程式人生 > >關於EF的導航屬性

關於EF的導航屬性

假如有三個實體,不相關的屬性不顯示

1、發票

    [Table("TShopInvoice")]
    public class ShopInvoiceModel
    {
        public int Id { get; set; }
        public int OrderId { get; set; }
        public int UserId { get; set; }
        public virtual UsersModel UsersModel { get; set; }
        public virtual ShopOrderModel ShopOrderModel { get; set; }
    }

2、訂單

    [Table("TShopOrder")]
    public class ShopOrderModel
    {
        [Key]
        public int id { get; set; }
        public int userid { get; set; }
        /// <summary>
        /// <summary>
        /// 訂單項集合
        /// </summary>
        public virtual List<ShopOrderGoodsModel> GoodsItem { get; set; }
        public virtual UsersModel UsersModel { get; set; }
        public virtual ICollection<ShopInvoiceModel> ShopInvoiceModel { get; set; }
    }

3、客戶

    [Table("TUser")]
    public class UsersModel
    {
        [Key]
        public int Id { get; set; }
        public virtual ICollection<ShopOrderModel> ShopOrderModel { get; set; }
        public virtual ICollection<ShopInvoiceModel> ShopInvoiceModel { get; set; }
    }

先搞清楚關係:

一個訂單可能會有幾張發票,但是一個發票只能對應一個訂單

一個客戶可能會有幾張發票,但是一個發票只可能開給一個客戶

配置一對多的關係(一對多與多對一的配置,應該是一樣的吧?):

ModelBuilder.Entity<ShopOrderModel>().HasMany(e => e.ShopInvoiceModel).WithRequired(e => e.ShopOrderModel).HasForeignKey(e => e.OrderId).WillCascadeOnDelete(false);
ModelBuilder.Entity<UsersModel>().HasMany(e => e.ShopInvoiceModel).WithRequired(e => e.UsersModel).HasForeignKey(e => e.UserId).WillCascadeOnDelete(false);

要達到可以通過點號點出相關物件,我們在查詢的時候必須載入相關資料。EF載入資料的方式,有預載入(Eager Loading)、延遲載入(Lazy Loading)、顯式載入(Explicit Loading)。不同的載入方式都有不同的適用情況,我們不能在這裡籠統地下決定說哪種方式好哪種方式不好。但有一點是需要遵循的,那就是如何提高資料載入的效率。此外,還有人提到按需載入的概念,其實那只是一種自創的方式,並不官方的方式。

延遲載入。在下面的程式碼中,首先會執行一次查詢,並將返回的結果存放在變數customers 中。而foreach迴圈會在每一次迴圈過程中獨立進行一次查詢,所以,如果資料庫表Customer中有100條記錄,那麼整個過程將產生101次查詢。

using (var dbcontext=  new ModelFirstDemoEntities())
{
            #region 延遲載入:用的時候載入
            var customers = from c in dbcontext.Customer
                            select c;

            foreach (var cus in customers)
            {
                Console.WriteLine(cus.Id);
                foreach (var order in cus.Order)  //根據導航屬性,自動查詢客戶的訂單
                {
                    Console.WriteLine(order.OrderContent);
                }
            }
            #endregion
}

預載入。如果你想讓所有資料一次性全部載入到記憶體中,那麼你需要使用.Include(Entity)方法。

using (var dbcontext=  new ModelFirstDemoEntities())
{
            #region 貪婪載入: 一次查詢載入全部資料
            var q = from t in dbcontext.Customer.Include("Order")
                    select t;

            foreach (var cus in q)
            {
                Console.WriteLine("Teacher : {0}", cus.Id);
                Console.WriteLine("Respective Courses...");
                foreach (var order in cus.Order)
                {
                    Console.WriteLine("Course name : {0}", order.OrderContent);
                }
                Console.WriteLine();
            }
            #endregion

}

只有一次資料互動過程,即程式只通過一次查詢便獲取到了所有需要的資料。它也可以減少程式與資料庫的互動次數。不過仍然有缺點,那就是如果資料量較大,一次性將所有資料載入記憶體往往並不是最明智的選擇。.Include(Entity)方法允許級聯使用,你可以預先載入具有多層級結構的資料。

顯式載入。顯式載入和延遲載入非常類似,不同的是顯式載入要手動關閉EF的延遲載入屬性,通過程式碼ctx.Configuration.LazyLoadingEnabled = false;來完成。

using (var dbcontext= new ModelFirstDemoEntities())
{
    dbcontext.Configuration.LazyLoadingEnabled = false;
            #region 顯式載入:查詢部分列資料,前提關閉 懶載入
            //查詢表中部分列的資料
            var items = from c in dbcontext.Customer
                        select c;
            foreach (var item in items)
            {
                //條件判斷,只加載滿足條件的資料,減少訪問資料庫的次數
                if (item.Id < 5)
                {
                    dbcontext.Entry(item).Collection(c => c.Order).Load();
                    Console.WriteLine(item.CusName);
                }

                foreach (var order in item.Order)
                {
                    Console.WriteLine("Course name : {0}", order.OrderContent);
                }
            }
            #endregion
}

按需載入。其實EF並不存在按需載入的概念,但是這種方式很值得說一說,在載入資料的時候並不是需要所有的屬性值,可能只需要一個Id,Name值。所以我們可以對要查詢的實體進行一下篩選,只加載自己需要的某些列,避免載入大量的垃圾資料。在這裡按需載入的概念只是載入需要的列。可能會與前端開發中 的概念“按需載入”有所衝突。

#region 按需載入:查詢部分列資料
//查詢表中部分列的資料
var items = from c in dbcontext.Customer
	where c.Id < 10
	select new { Id = c.Id, CName = c.CusName, OrderCount = c.Order.Count() };
foreach (var item in items)
{
Console.WriteLine(item.CName);
}
#endregion

回到文章開頭的問題:

public static List<ShopInvoiceModel> GetByFilter(DateTime start, DateTime end, int pageIndex, int pageSize, ref int count)
{
    using (NMProjectContext db = new NMProjectContext())
    {
	//以下第一行是預載入模式,不用為好,所資料量太大
	//IQueryable<ShopInvoiceModel> query = from tb in db.ShopInvoiceModel.Include("ShopOrderModel").Include("UsersModel") where tb.CreateTime >= start && tb.CreateTime <= end select tb;
	IQueryable<ShopInvoiceModel> query = from tb in db.ShopInvoiceModel where tb.CreateTime >= start && tb.CreateTime <= end select tb;
	query = query.OrderByDescending(a => a.Id);
	count = query.Count();
	//以下兩行不行,DBContext.Entry(object)只接受單一物件,不接受List或者IQueryable<Entry>
	//db.Entry(query).Reference("ShopOrderModel").Load();
	//db.Entry(query).Reference("UsersModel").Load();
	List<ShopInvoiceModel> r = query.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
	r.ForEach(p => db.Entry(p).Reference("ShopOrderModel").Load());
	//或者如下方式
	//r.ForEach(p => db.Entry(p).Reference(x=>x.ShopOrderModel).Load());
	r.ForEach(p => db.Entry(p).Reference("UsersModel").Load());
	// 如果要從單一物件(比如使用者)找出其相關的所有發票,就用:
	//context.Entry(userobject).Collection(p => p.ShopInvoiceModel).Load();
	//context.Entry(userobject).Collection("ShopInvoiceModel").Load();
	return r;
    }
}

如果使用sqlquery則是如下方式:

System.Data.SqlClient.SqlParameter[] param = { whereP, totalRecordP, totalPageP, pageSizeP, pageIndexP, tableNameP, orderFieldP, AscOrDescP };
var results = db.Database.SqlQuery<ShopInvoiceModel>("exec dbo.GetList  @where, @totalRecord OUTPUT, @TotalPage OUTPUT, @pageSize, @pageIndex,@tablename,@orderField,@AscOrDesc", param);
List<ShopInvoiceModel> list = results.ToList();
list.ForEach(p => db.ShopInvoiceModel.Attach(p));
list.ForEach(p => db.Entry(p).Reference(x => x.ShopOrderModel).Load());
list.ForEach(p => db.Entry(p).Reference(x => x.UsersModel).Load());
count = Convert.ToInt32(totalRecordP.Value);
totalPage = Convert.ToInt32(totalPageP.Value);
return list;

Using Query to count related entities without loading them.Sometimes it is useful to know how many entities are related to another entity in the database without actually incurring the cost of loading all those entities. The Query method with the LINQ Count method can be used to do this. For example:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Count how many posts the blog has  
    var postCount = context.Entry(blog)
                           .Collection(b => b.Posts)
                           .Query()
                           .Count();
}