Приведение Entity Framework не поддерживается

#c# #entity-framework

#c# #entity-framework

Вопрос:

я надеюсь, что кто-нибудь может мне помочь с этим. Я использую EF6 с TPC (таблица для каждого конкретного класса). Существует много таблиц, которые сохраняются в редакции, что означает, что каждая строка в таблице имеет один и тот же идентификатор, но несколько версий. Одна из версий — это текущая версия (например). Пример таблицы student:

 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 amp;amp; 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 resu<
            }
        }
    }
  

Это полностью работает, но, к сожалению, мне приходится загружать все строки с одинаковым идентификатором и фильтровать текущую версию в памяти. (ToList загружает все строки с заданным идентификатором.)

Есть ли какие-либо лучшие способы?

Я не хочу, чтобы этот «ToList» был там. Что мне нужно, так это либо приведение, чтобы проверить мою текущую версию, либо, возможно, смешать цепочку методов и жестко закодированную строку фильтра. (не очень хорошо, но намного лучше, чем это) Я знаю, что в этой таблице есть столбец CurrentVersion. Я хочу проверить это перед загрузкой всех записей.

Ответ №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();
    }
}
  

Комментарии:

1. Отличный Иван, спасибо. Я проверил первую версию, и она действительно работает. В БД отправляется только один оператор sql. Этот оператор sql содержит оба условия where . Теперь я попробую второй способ просто для себя, чтобы научиться…

2. вау, второй способ тоже работает! к сожалению, я могу проголосовать только один раз!

3. кстати, после того, как я проверил второй способ, он мне нравится больше, чем первый. мне не нужен другой метод, поэтому я беру этот, но я оставлю первый в комментарии, чтобы напомнить мне позже.

Ответ №2:

Вы можете создать модель, содержащую свойства, а затем использовать метод расширения приведения для приведения к соответствующему типу. Затем выполните простой 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;
}
  

Комментарии:

1. Спасибо за эту идею, но это не может быть полезным способом. это много кода отражения, и также создается много новых экземпляров. я только хочу проверить столбец, который у меня действительно есть в моем 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*/);