#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*/);