已經有一陣沒有寫博客了,並不是不想寫,只不過最近的項目實在事太多沒時間總結,最近項目客戶提到了網站性能問題,這讓我不得不反思自己在項目中應EntityFramework是不是有些地方應該可以優化的。
常見問題:
1:EntityFramework如何在DbModel(數據庫模型)與DoaminModel(領域模型)之間進行DTO?
什么是DTO,就是數據轉換,我們從數據庫中查詢出來的是數據庫模型對象,和數據庫表結構是對應上的,但領域模型是給業務功能模塊使用的,字段名稱等各方面都有可能不相同。
方法一:自己編寫反射方法,進行屬性復制;下面是部分示例代碼
public T ReturnModel<T, TS>(TS c) where T : new() { var result = new T(); var properties = typeof(T).GetProperties(); var sProperties = typeof(TS).GetProperties(); foreach (var propertyInfo in properties) { foreach (var spropertyInfo in sProperties) { if (propertyInfo.Name.Equals(spropertyInfo.Name)) { if (propertyInfo.PropertyType.FullName != null && propertyInfo.PropertyType.FullName.Equals(spropertyInfo.PropertyType.FullName)) { propertyInfo.SetValue(result, spropertyInfo.GetValue(c, null), null); }
方法二:利用一些自動化的工具,比如AutoMapper,這個東東我就不貼代碼了,是個開源的,下載源碼看下就行。
問題:
如果采用以上兩個方法進行數據庫與領域模型之間DTO,那么默認情況下,EntityFramework會將關聯到的表的所有字段取到客戶端,不管你用還是不用,前提是已經關閉了EntityFramework延遲加載屬性。
2:EntityFramework能否只返回我們所需要的字段?
既然我們需要DTO,又有我們不想看到的select * 的效果,那么如何按需所取呢?
答案是肯定的,借用我同事的一句話就是:如果EntityFramework這點事也解決不了微軟也不好意思拿出來呀。
常見解決方案:根據不同的方法編寫不同的Select,比如:
var dataList = dao.Query(newfilter).Select( p => new ObjectModel.Specialty { Id = p.Specialty.Id, Name = p.Specialty.Name } ).Distinct().ToList();
這種方案並沒有什么不好,只不過需要根據自己的需求編寫不同的數據庫查詢方法。
最近看見一老外寫了篇不錯的文章(停止在數據庫訪問層中運用AutoMapper),正好符合我的需求,它的主意就是將自己需要的數據整合在一個領域模型中,程序員不再需要編寫不同的后台查詢,只需要按需求定義好領域模型就可以了。
http://www.devtrends.co.uk/blog/stop-using-automapper-in-your-data-access-code
比如有如下表結構:
需求:查詢所有學生信息,並且顯示每個學生的老師姓名,老師類型信息。
也許我們需要這樣寫:
var dataList = dao.Query(newfilter).Select( p => new ObjectModel.Specialty { StudentName = p.Name, TeacherName=p.Teacher.Name, TeacherTypeName=p.Teacher.TeacherType.Name } ).ToList();
為何不這樣寫:
首先我們定義領域模型:
public class StudentSummary { public string FirstName { get; set; } public string LastName { get; set; } public string Teacher_Name { get; set; } public string Teacher_TeacherType_Name { get; set; } }
程序調用:
var students = context.Students.Project().To<StudentSummary>();
多么簡潔!來看看它的實現原理:利用反射得到數據庫模型以及領域模型的屬性信息,然后我們利用按照一定規則定義的領域模型進行動態表達式樹(主要是Select表達式樹的構建)的構建,比如上面的StudentSummary中的Teacher_Name,為什么中間有一個"_",其實這是我自己定義的一種規則,用以區分是簡單屬性還是復雜屬性,說明下這里的簡單屬性就是指數據類型是int,string這一流的,復雜屬性就是指Teacher這種Class型的。
老外的項目是利用大寫字母來區分的,定義成TeacherName,這將會分成兩個詞:Teacher,以及Name,那么他認為這個Teacher是一個復雜屬性,最后面的Name是一個簡單屬性。而我在些基礎上多增加了一層支持,比如我們訪問學生的老師的類型:student.Teacher.TeacherType.Name,如果.net定義屬性能使用"."那么我會在Student上定義這樣的屬性:Teacher.TeacherType.Name,但不允許,我只能用"_"代替,盡管看起來有些別扭,但好在解決了我的問題。
下面是我按自己的需求修改過的擴展代碼:

public static class QueryableExtensions { public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source) { return new ProjectionExpression<TSource>(source); } } public class ProjectionExpression<TSource> { private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>(); private readonly IQueryable<TSource> _source; public ProjectionExpression(IQueryable<TSource> source) { _source = source; } public IQueryable<TDest> To<TDest>() { var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>(); return _source.Select(queryExpression); } private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>() { var key = GetCacheKey<TDest>(); return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null; } private static Expression<Func<TSource, TDest>> BuildExpression<TDest>() { var sourceProperties = typeof(TSource).GetProperties(); var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite); var parameterExpression = Expression.Parameter(typeof(TSource), "src"); var bindings = destinationProperties .Select(destinationProperty => BuildBinding(parameterExpression, destinationProperty, sourceProperties)) .Where(binding => binding != null); var expression = Expression.Lambda<Func<TSource, TDest>>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression); var key = GetCacheKey<TDest>(); ExpressionCache.Add(key, expression); return expression; } private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable<PropertyInfo> sourceProperties) { var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == destinationProperty.Name); if (sourceProperty != null) { return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty)); } var propertyNames = SplitTableName(destinationProperty.Name); if (propertyNames.Length == 2) { sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == propertyNames[0]); if (sourceProperty != null) { var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[1]); if (sourceChildProperty != null) { return Expression.Bind(destinationProperty, Expression.Property(Expression.Property(parameterExpression, sourceProperty), sourceChildProperty)); } } } else if (propertyNames.Length == 3) { var firstSourceProperty = sourceProperties.FirstOrDefault(src => src.Name == propertyNames[0]); if (null == firstSourceProperty) return null; var secondSourceProperty = firstSourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[1]); if (null == secondSourceProperty) return null; var sourceChildProperty = secondSourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[2]); if (sourceChildProperty != null) { var secondProp=Expression.Property( parameterExpression ,firstSourceProperty ); var firstProp = Expression.Property(secondProp, secondSourceProperty); var resultProp=Expression.Property( firstProp , sourceChildProperty); return Expression.Bind(destinationProperty, resultProp); } } else if (propertyNames.Length == 1) { return null; } else { throw new NotSupportedException("do not suports this kind of operation"); } return null; } private static string GetCacheKey<TDest>() { return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName); } private static string[] SplitTableName(string input) { return input.Trim().Split('_'); } }
EntiryFrameWork還是很強大的,對付一般小型項目基本不會存在性能問題,除非您的小項目會有大數據量的情況,大多數情況都是使用不當引起的性能問題。