【问题标题】:Entity Framework Cast not supported不支持实体框架转换
【发布时间】:2016-10-08 01:25:22
【问题描述】:

我希望有人可以帮助我解决这个问题。我将 EF6 与 TPC 一起使用(每个具体类的表)。有许多表是修订保存的,这意味着表中的每一行都有相同的 ID,但有多个版本。一个版本是当前版本(例如)。 表学生样本:

Id  Version     Name        CurrentVersion
1   1           Murtin      false
1   2           Martin      true
2   1           Reinold     true

我在模型中使用继承。所有模型的基础都是EntityBase

abstract class EntityBase: IEntityBase
{
    [Key]
    [Column(Order = 0)]
    public int Id { get; set; }
}

对于所有修订保存的表,我使用下一个基类

abstract class RevisionBase : EntityBase, IRevisionBase
{
    [Key]
    [Column(Order = 1)]
    public int Version { get; set; }

    [Required]
    public bool CurrentVersion { get; set; }
}

现在我和我一起工作的桌子,当然是学生

class Student: RevisionBase
{
    [Required]
    public string Name{ get; set; }
}

所以下面的代码既简单又有效

new DBContext().Student.FirstOrDefault(s=>s.Id=5 && s.CurrentVersion=true)

对于统一的数据处理,我希望始终使用相同的基本方法进行加载。所以我有统一的错误处理和日志记录。这个通用的 get 方法应该总是加载当前版本,如果它是一个版本化的表。

    private TEntity GeneralGet<TEntity>(int id) where TEntity : EntityBase
    {
        using (var ctx = GetContext())
        {
            if (typeof(IRevisionBase).IsAssignableFrom(typeof(TEntity)))
            {
                var allResultsId = ctx.Set<TEntity>().Where(l => l.Id == id);
                var result = allResultsId.ToList().Cast<IRevisionBase>().FirstOrDefault(r => r.CurrentVersion);
                return result as TEntity;
            }
            else        // ansonsten, bei nicht versionierten Objekten
            {
                var result = ctx.Set<TEntity>().FirstOrDefault(l => l.Id == id);
                return result;
            }
        }
    }

这完全有效,但不幸的是我必须加载相同 id 的所有行并过滤内存中的当前版本。 (ToList 加载具有给定 id 的所有行。)

有没有更好的方法?

我不想要这个“ToList”。我需要的是检查我当前版本的演员表,或者混合方法链方式和硬编码过滤器字符串。 (不好,但比这好得多)我知道这个表有一列 CurrentVersion。我想在加载所有记录之前检查一下。

【问题讨论】:

    标签: c# entity-framework


    【解决方案1】:

    EF 不喜欢强制转换(以及一般的接口),C# 不允许“强制转换”类型。

    您的要求仍然是可能的。我看到至少两个(可能不完美,但有效)选项:

    (1) 像这样创建一个通用的约束方法:

    static IQueryable<TEntity> WhereCurrentVersion<TEntity>(IQueryable<TEntity> source)
        where TEntity : class, IRevisionBase
    {
        return source.Where(e => e.CurrentVersion);
    }
    

    并通过反射或动态调用它:

    private TEntity GeneralGet<TEntity>(int id) where TEntity : EntityBase
    {
        using (var ctx = GetContext())
        {
            var result = ctx.Set<TEntity>().Where(e => e.Id == id);
            if (result is IQueryable<IRevisionBase>)
                result = WhereCurrentVersion((dynamic)result);
            return result.FirstOrDefault();
        }
    }
    

    (2)动态构建过滤器表达式:

    private TEntity GeneralGet<TEntity>(int id) where TEntity : EntityBase
    {
        using (var ctx = GetContext())
        {
            var result = ctx.Set<TEntity>().Where(e => e.Id == id);
            if (result is IQueryable<IRevisionBase>)
            {
                var parameter = Expression.Parameter(typeof(TEntity), "e");
                var predicate = Expression.Lambda<Func<TEntity, bool>>(
                    Expression.Property(parameter, nameof(IRevisionBase.CurrentVersion)),
                    parameter);
                result = result.Where(predicate);
            }
            return result.FirstOrDefault();
        }
    }
    

    【讨论】:

    • 伟大的伊万,谢谢。我检查了第一个版本,它确实有效。只向数据库发送一条 sql 语句。此 sql 语句包含两个 where 条件。现在我将尝试第二种方式,只是为了自己学习......
    • 哇,第二种方法也有效!不幸的是,我只能投票一次!
    • 顺便说一句,在我检查了第二种方式之后,我比第一种更喜欢它。我不需要其他方法,所以我采用这个,但我会保留第一个方法,以便稍后提醒我。
    【解决方案2】:

    您可以制作一个包含属性的模型,然后您可以使用 Cast Extension 方法将其转换为适当的类型。然后执行简单的 LINQ。

    public static T Cast<T>(this Object myobj)
    {
        Type objectType = myobj.GetType();
        var dx = myobj.GetType().GetProperties();
        Type target = typeof(T);
        var x = Activator.CreateInstance(target, false);
        var z = from source in objectType.GetMembers().ToList()
                where source.MemberType == MemberTypes.Property
                select source;
        var d = from source in target.GetMembers().ToList()
                where source.MemberType == MemberTypes.Property
                select source;
        List<MemberInfo> members = d.Where(memberInfo => d.Select(c => c.Name).ToList().Contains(memberInfo.Name)).ToList();
        PropertyInfo propertyInfo;
        object value;
        foreach (var memberInfo in members)
        {
            propertyInfo = typeof(T).GetProperty(memberInfo.Name);                
            if (Array.Exists(dx, a => a.Name == propertyInfo.Name))
            {
                value = myobj.GetType().GetProperty(memberInfo.Name).GetValue(myobj, null);
                propertyInfo.SetValue(x, value, null);
            }
        }
        return (T)x;
    }
    

    【讨论】:

    • 感谢您的想法,但这不是一个有用的方法。这是很多反射代码,并且还创建了很多新实例。我只想检查我在我的 EF 过滤器中实际拥有的一列...
    【解决方案3】:
    var tmp = LocalDB.Article.Select(A => A.Matricule).ToArray();
    

    你可以做这样的事情,你将有一个你想要的值的数组,然后你可以使用你想要的任何方法或类型将它们添加到另一个数组或列表中......

    或者你可以从一开始就做这样的事情

    var list = new List<T>();
    foreach(var item in LocalDB.Article.Select(A => A.Matricule).ToArray()) 
    list.Add(/*Transform item to your wanted type*/);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多