在 DbSet<T> 中查找 Max(Id),其中 T 是未知的。

3

如何找到DbSet.Set<T>()中最大的Id

注意: 不是DbSet<TEntity>

在运行时,我不知道类型。

背景: 我有20个表/实体,使用通用方法进行处理。

该过程涉及查找该表/实体的最大Id,并将其与手头的记录进行比较。

如果记录的id大于数据库中的,则将其插入数据库。

到目前为止,我尝试使用反射:

DbSet<T> table = DbContext.Set<T>();
var lastRecord = table.LastOrDefault();  // throws not supported error
var idProperty = lastRecord.GetType().GetProperties()
                                     .FirstOrDefault(p => p.Name.Equals("Id");  
int maxId = (int)idProperty.GetValue(lastRecord);

我也尝试过使用接口转换:

interface ICommonEntity
{                              // this interface allows the generic method
    string StringId { get;}    // to know how to handle entity Id's of 
    int? IntId { get; }        // different types (string vs int).
}                              

var whatever =  table.OrderByDescending(e => (e as ICommonEntity).IntId).FirstOrDefault();
int maxId = (whatever as ICommonEntity).IntId ?? 0;

但是上述代码会产生以下错误:

带有类型 xx 的'TypeAs'表达式不受支持,以及类型 yy 的检查。在LINQ to Entities查询中,只支持实体类型和复杂类型。

附加数据: 我的所有实体都有一个名为Id的列 / 属性,其类型为int
我所做的网络搜索主要指向已知类型的解决方案,例如TEntity,db.Users.xxx()等。


更新

针对Ian答案,我不能直接使用Id为什么?
我的一个实体具有名为Id的字段,但其类型为string

class EntityStringId : ICommonEntity
{
    public string Id { get; set; }
    public string StringId => Id;
    public int? IntId => null;
}

class EntityIntId : ICommonEntity
{
    public int Id { get; set; }
    public string StringId => null;
    public int? IntId => Id;
}

如果我尝试使用IntId进行排序,

private void SomeMethod<T>(string file)
    //where T : class           // original
    //where T : ICommonEntity   // cannot. DbContext.Set<T>(); requires class
    where T : class, ICommonEntity  // throws exception
    {
        var table_T = DbContext.Set<T>();
        var maxId = table_T.Max(e => e.IntId); // throws exception ↓
    }

在LINQ to Entities中不支持指定类型成员“IntId”。只支持初始化器、实体成员和实体导航属性。

为了更好地理解,我的方法逻辑:

private void ProcessCsvToDb<T>(
        DbSet<T> table,
        T csvRecord) where T : class
{
    var iRecord = csvRecord as ICommonEntity;
    T dbRecord = null;
    if (!string.IsNullOrEmpty(iRecord.StringId))
    {
        dbRecord = table.Find(iRecord.StringId);
    }
    else if (iRecord.IntId != null)
    {
        dbRecord = table.Find(iRecord.IntId);
    }
}

你在后面的例子中暗示所有实体都继承自 ICommonEntity 接口了吗?你能否只在方法中使用泛型约束呢? - Ian Mercer
是的,它们都实现了ICommonEntity接口。我使用了DbContext.Set<T>()所要求的泛型约束。 - user8667400
如果你的方法使用了泛型约束 where T:ICommonEntity,为什么还需要使用 as 进行强制转换呢? - Ian Mercer
3个回答

5
为了在没有基类/接口的情况下完成这个任务,您需要手动组合表达式:
public static IOrderedQueryable<int> OrderById(Type entityType)
{
    var dbSet = context.Set(entityType);

    var item = Expression.Parameter(entityType, "item");
    var property = Expression.Property(item, "Id");
    var lambda = Expression.Lambda<Func<T, int>>(property, item);
    // the above generates:
    // item => item.Id

    return dbSet.OrderByDescending(lambda);
}

1
你可以构建一个按Id排序的表达式,但DynamicQueryable类已经为您完成了这个功能:
DbSet<T> table = assignFromSomeWhere();
var maxId = table.OrderBy("Id desc").FirstOrDefault();
< p > DynamicQueryable 还为您提供不同的扩展方法(动态 Where、Select)。显然,自己构建表达式更令人满意,但有时非常复杂,这个库可以帮助很多。


0

如果您有一个接口,正如评论中所讨论的那样,是否有任何理由避免进行强制转换:

public static int? GetMaxId<T>(DBSet<T> dbSet)
  where T : ICommonEntity
{
  return dbSet.OrderByDescending(e => e.Id).FirstOrDefault();
}

在尝试此操作时,我遇到了以下错误:“指定的类型成员'IntId'在LINQ to Entities中不受支持。仅支持初始化程序、实体成员和实体导航属性。” - user8667400
@TanSam 为什么接口使用 IntId 而实际实体使用 Id - Ian Mercer
原因是我创建了这个接口来处理具有字符串ID和整数ID的实体。所以它是一个混合体。这很复杂 ;) - user8667400
我会在有时间的时候尝试这个。目前这个方法会导致一些错误,因为它需要像DbSet<T>所要求的那样是T:class类型。 - user8667400

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接