• Linq To Entity经验:如何解决按需所取


        已经有一阵没有写博客了,并不是不想写,只不过最近的项目实在事太多没时间总结,最近项目客户提到了网站性能问题,这让我不得不反思自己在项目中应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,但不允许,我只能用"_"代替,尽管看起来有些别扭,但好在解决了我的问题。

         下面是我按自己的需求修改过的扩展代码:

    View Code
    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还是很强大的,对付一般小型项目基本不会存在性能问题,除非您的小项目会有大数据量的情况,大多数情况都是使用不当引起的性能问题。

  • 相关阅读:
    texarea动态改变监听
    输入配置
    linux 上安装redis 解压之后使用make命令报错
    sql语句能查询出 放在web程序查询不出
    Eclipse 使用git pull 代码时发生冲突的解决办法
    eclipse git 解决冲突
    微信小程序获取openid
    mybatis 一对多 一对一 xml配置
    Eclipse 中撤消git push提交
    idea 开发中遇到的问题
  • 原文地址:https://www.cnblogs.com/ASPNET2008/p/2985796.html
Copyright © 2020-2023  润新知