【问题标题】:Linq over generic class with generic property具有泛型属性的泛型类上的 Linq
【发布时间】:2015-06-19 23:29:50
【问题描述】:

问题是关于在一系列元素中获取最大值。 对象类型未知,属性遵循约定<EntityName>_Id

目前我是这样解决的:

public static int GetMaxId<TEntity>()
{
    try
    {
        var key = typeof(TEntity).Name;
        var adapter = (IObjectContextAdapter)MyDbContext;
        var objectContext = adapter.ObjectContext;
        // 1. we need the container for the conceptual model
        var container = objectContext.MetadataWorkspace.GetEntityContainer(
            objectContext.DefaultContainerName, System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace);
        // 2. we need the name given to the element set in that conceptual model
        var name = container.BaseEntitySets.Where((s) => s.ElementType.Name.Equals(key)).FirstOrDefault().Name;
        // 3. finally, we can create a basic query for this set
        var query = objectContext.CreateQuery<TEntity>("[" + name + "]");

        // working with result
        var baseEntity = container.BaseEntitySets.Where((s) => s.ElementType.Name.Equals(key)).FirstOrDefault();
        var property = baseEntity.ElementType.Members.FirstOrDefault(_property => _property.Name.EndsWith("Id"));

        var tmpResult = query.ToList();

        var currentMaxID = tmpResult.Max(element =>
        {
            PropertyInfo pInfo = typeof(TEntity).GetProperty(property.Name);
            int value = (int)pInfo.GetValue(element);

            return value;
        });

        return currentMaxID;
    }
    catch (Exception ex)
    {
        string exMessage = "GetMaxId.Exception";
        EntityModelDataProviderLogger.Fatal(exMessage, ex);
        throw;
    }
}

有更好/更清洁的方法吗?

我读过关于动态表达式的文章,但对我来说不是很清楚。

LINQ expression with generic property 此示例比较日期。就我而言,我“只有一个元素”,我不知道如何构造表达式。

【问题讨论】:

    标签: c# linq entity-framework generics


    【解决方案1】:

    比较长,这里没法测试:

    public static int GetMaxId<TEntity>()
    {
        try
        {
            var key = typeof(TEntity).Name;
            var adapter = (IObjectContextAdapter)MyDbContext;
            var objectContext = adapter.ObjectContext;
    
            // 1. we need the container for the conceptual model
            var container = objectContext.MetadataWorkspace.GetEntityContainer(
                objectContext.DefaultContainerName, System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace);
            // 2. we need the name given to the element set in that conceptual model
            var baseEntity = container.BaseEntitySets.Single(s => s.ElementType.Name == key);
            // 3. finally, we can create a basic query for this set
            var query = objectContext.CreateQuery<TEntity>("[" + baseEntity.Name + "]");
    
            // Looking for the property
            string propertyId = baseEntity.Name + "_" + "Id";
            // The PropertyInfo connected to the EdmMember
            PropertyInfo property = (PropertyInfo)typeof(TEntity).GetProperty(propertyId);
    
            // Building the Expression
            ParameterExpression par = Expression.Parameter(typeof(TEntity));
            MemberExpression prop = Expression.Property(par, property);
    
            // cast to (int?)property
            // Note the use of int?, because the table could be empty!
            UnaryExpression conv = Expression.Convert(prop, typeof(int?));
    
            // An expression like x => x.Entity_Id
            Expression<Func<TEntity, int?>> lambda = Expression.Lambda<Func<TEntity, int?>>(conv, par);
    
            int? currentMaxID = ((IQueryable<TEntity>)query).Max(lambda);
    
            // We change null to 0
            return currentMaxID ?? 0;
        }
        catch (Exception ex)
        {
            string exMessage = "GetMaxId.Exception";
            EntityModelDataProviderLogger.Fatal(exMessage, ex);
            throw;
        }
    }
    

    如果它有效,我将解释它如何/为什么有效......

    请注意,您所做的是概念上的错误...您使用ToList() 选择所有表,然后在本地查找Max()。此代码改为创建一个表达式树来执行 Max() 数据库端。

    【讨论】:

    • 这一行PropertyInfo property = (PropertyInfo)edmMember.MetadataProperties.Single(x =&gt; x.Name == "ClrPropertyInfo").Value; 抛出异常。我检查了MetadataProperties,它在我的测试用例中包含三个元素(名称:Name、TypeUsage、MetadataProperties)
    • @blacai 直接使用反射。立即尝试。
    • 我不会说“概念上的错误”。这里没有速度问题,只有内存。我质疑写 60 行 C# 的价值,而不是用 1 行 sql 做的事情。
    • 如果您在编译时已经知道 Max 方法的类型参数,为什么还要通过反射搜索它?
    • @Georg 因为你是对的! :-) 有时一个人有一种心态,像火车一样行驶...... 5 分钟后就会改正
    【解决方案2】:

    我将定义一个带有属性 Id 的接口,并在您的方法上添加一个约束,以仅接受该接口的类型。这消除了对脆弱的命名约定的需求,并且不需要反思。

    public interface IEntity
    {
        int Id { get; set; }
    }
    
    public static int GetMaxId<TEntity>() where TEntity : IEntity
    {
        return MyDbContext.Set<TEntity>().AsEnumerable().Max(e => e.Id);
    }
    
    public Entity : IEntity
    {
        public int MyId { get; set;}
    
        public int Id { get{ return MyId; } set{ MyId = value; } }
    }
    

    用法:

    var max = GetMaxId<Entity>();
    

    【讨论】:

    • 有了这个解决方案,我应该必须修改EntityFramework生成的所有类,才能实现接口,对吧?
    • 任何你打算使用这个方法的类都可以。
    • 有办法做到这一点,只需拿出实体键并比较它们我目前找不到示例
    • @Shoe 不清楚为什么有两个IdIdMyId)...这两个应该映射到Db 中的哪一个?另一个有什么用?
    • @xanatos MyId 是数据库映射 ID。 Id 来自界面。该接口提供了为 Id 字段指定任何名称的方法,而不是跟踪任意约定。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-23
    • 1970-01-01
    • 2011-10-08
    • 1970-01-01
    • 2019-05-29
    • 2016-05-13
    • 2023-03-02
    相关资源
    最近更新 更多