Как использовать свойства интерфейса с помощью CodeFirst
У меня есть следующие объекты:
public interface IMyEntity
{
[Key]
int Id { get; set; }
IMyDetail MyDetail { get; set; }
ICollection<IMyDetail> CollectionOfReferences { get; set; }
}
public interface IMyDetail
{
[Key]
int Id { get; set; }
int IntValue { get; set; }
}
public class MyEntity : IMyEntity
{
[Key]
public virtual int Id { get; set; }
public virtual IMyDetail MyDetail { get; set; }
public virtual ICollection<IMyDetail> CollectionOfReferences { get; set; }
}
public class MyDetail : IMyDetail
{
[Key]
public virtual int Id { get; set; }
public virtual int IntValue { get; set; }
}
Я хочу использовать EF CodeFirst для доступа к базе данных и создания схемы базы данных. Но CodeFirst не позволяет использовать типы интерфейсов для отношений между объектами. Поэтому он не создает связи между MyEntity и MyDetail. Я не могу изменить интерфейсы, поэтому я не могу изменить тип свойства в MyDetail вместо IMyDetail. Но я знаю, что клиент этой модели будет использовать только одну реализацию каждого интерфейса.
Я нашел обходное решение для свойств типа IMyDetail. Я могу создать свойство типа MyDetail и явно реализовать свойство интерфейса:
private MyDetail _myDetail;
public virtual MyDetail MyDetail
{
get
{
return this._myDetail;
}
set
{
this._myDetail = value;
}
}
IMyDetail IMyEntity.MyDetail
{
get
{
return this._myDetail;
}
set
{
this._myDetail = (MyDetail)value;
}
}
Он отлично работает. Но это решение не работает с ICollection<IMyDetail>
, потому что я не могу отнести его к ICollection<MyDetail>
.
Существуют ли какие-либо решения для этого?
Ответы
Ответ 1
Неправильное решение состоит в том, чтобы просто объединить эти интерфейсы, которые вы хотите сохранить в базовых классах, и разбивать базовые объекты на подклассы. EF поддерживает это, и если вы идете с таблицей на иерархию (по умолчанию), вы можете отсортировать все базовые объекты подкласса с помощью общего свойства, используя обычный запрос LINQ из EF вместо того, чтобы обладать лукавством и делать что-то вроде write raw SQL или получить несколько списков в памяти и отсортировать объединение без помощи БД, например, с помощью решения Cel интерфейсов и адаптеров.
Вы также можете использовать дочерние/родительские типы интерфейсов как generics, так что, когда разработчик использует конкретные классы в Db, они могут в основном использовать ваши интерфейсы, но скажите EF, чтобы использовать конкретные классы:
public interface IParent<out TChild>
where TChild : IChild
{
ICollection<TChild> Children { get; set; }
Кто-то может создать свои классы Db, например:
public class Parent : IParent<Child>
. . .
Но все равно используйте их как:
IParent<IChild> parents = db.Parents.Include(p => p.Children).ToArray();
Поскольку общий знак отмечен out
, общий ковариант, и поэтому может принимать все, что соответствует общим ограничениям, включая приведенные выше преобразует дерево типов в интерфейс IChild.
Тем не менее, если вы действительно хотите сохранить интерфейсы, правильным ответом является, вероятно, использование NHibernate:
Как сопоставить интерфейс в nhibernate?
И некоторые кодеры рекомендуют вам поддерживать интерфейсы для сущностей в любом ORM, ограниченном несколькими совместно используемыми свойствами, или рисковать неправильным использованием:
Программирование интерфейсов при сопоставлении с Fluent NHibernate
Ответ 2
Обходной путь заключается в создании специальной реализации для каждого интерфейса, который вы хотите использовать с Entity Framework, используя шаблон адаптера:
Wrapper для интерфейса
// Entity Framework will recognize this because it is a concrete type
public class SecondLevelDomainRep: ISecondLevelDomain
{
private readonly ISecondLevelDomain _adaptee;
// For persisting into database
public SecondLevelDomainRep(ISecondLevelDomain adaptee)
{
_adaptee = adaptee;
}
// For retrieving data out of database
public SecondLevelDomainRep()
{
// Mapping to desired implementation
_adaptee = new SecondLevelDomain();
}
public ISecondLevelDomain Adaptee
{
get { return _adaptee; }
}
public string Id
{
get { return _adaptee.Id; }
set { _adaptee.Id = value; }
}
// ... whatever other members the interface defines
}
Пример сохранения и загрузки
// Repositor is your DbContext
public void SubmitDomain(ISecondLevelDomain secondLevelDomain)
{
Repositor.SecondLevelDomainReps.Add(new SecondLevelDomainRep(secondLevelDomain));
Repositor.SaveChanges();
}
public IList<ISecondLevelDomain> RetrieveDomains()
{
return Repositor.SecondLevelDomainReps.Select(i => i.Adaptee).ToList();
}
Использование навигационных свойств/внешних ключей/родительских дочерних сопоставлений
Для более сложных интерфейсов/классов вы можете получить InvalidOperationException - см. Конфликт изменений с первым внешним ключом кода в Entity Framework для реализации, которая работает с такими иерархиями объектов
Ответ 3
После нескольких бессонных ночей я считаю, что нашел решение этой проблемы. Я тестировал этот подход (немного) и, похоже, работает, но ему, вероятно, нужны еще несколько глазных яблок, чтобы разорвать его на части и объяснить, почему этот подход может сломаться. Я использовал FluentAPI для настройки атрибутов базы данных вместо украшения свойств класса сущности. Я удалил виртуальный атрибут из членов класса сущности (я предпочитаю использовать явные включения вместо того, чтобы возвращаться к ленивой загрузке для дочерних объектов). Я также немного переименовал классы классов и свойства, чтобы он стал яснее для меня. Я предполагаю, что вы пытаетесь выразить отношения "один ко многим" между сущностью и ее деталями. Вы пытаетесь реализовать интерфейсы для своих объектов в уровне хранилища, чтобы верхние уровни были агностическими для классов сущностей. Более высокие уровни знают только интерфейсы, а не сами сущности...
public interface IMyEntity
{
int EntityId { get; set; }
//children
ICollection<IMyDetailEntity> Details { get; set; }
}
public interface IMyDetailEntity
{
int DetailEntityId { get; set; }
int EntityId { get; set; }
int IntValue { get; set; }
//parent
IEntity Entity { get; set; }
}
public class MyEntity : IMyEntity
{
public int EntityId { get; set; }
private ICollection<IMyDetailEntity> _Details;
public ICollection<MyDetailEntity> Details {
get
{
if (_Details == null)
{
return null;
}
return _Details.Select(x => (MyDetailEntity) x).ToList();
}
set
{
_Details = value.Select(x => (IMyDetailEntity) x).ToList();
}
}
ICollection<IMyDetailEntity> IMyEntity.Details
{
get
{
return _Details;
}
set
{
_Details = value;
}
}
}
public class MyDetailEntity : IMyDetailEntity
{
public int DetailEntityId { get; set; }
public int EntityId { get; set; }
public int IntValue { get; set; }
private IMyEntity _Entity;
public MyEntity Entity
{
get
{
return (Entity)_Entity;
}
set
{
_Entity = (Entity)value;
}
}
IEntity IMyDetailEntity.Entity
{
get
{
return _Entity;
}
set
{
_Entity = value;
}
}
}
Ответ 4
Этот параметр не работает для вас? Я обнаружил, что это удовлетворяет требованию Интерфейса, и оно работает так же, как и ваше другое объявление явного интерфейса:
public interface IPet { }
public class Pet : IPet { }
public interface IPerson
{
IEnumerable<IPet> Pets { get; }
}
public class Person : IPerson
{
public virtual ICollection<Pet> Pets { get; set; }
IEnumerable<IPets> Pets
{
get { return Pets.OfType<IPet>().ToList(); }
}
}
public class RepositoryDataAccessWhatever
{
public IPerson GetPerson()
{
return _dbContext.Persons
.Include(p => p.Pets)
.FirstOrDefault();
}
}
Ответ 5
У меня тоже была эта проблема на некоторых моделях, а не на других, и я попытался использовать принятый ответ. Затем я немного углубился в отношении того, что было по-другому на моделях, которые работали.
Исправлено было изменение с использованием ICollection, чтобы использовать IEnumerable, и проблемы ушли до сих пор.
Это устранило необходимость использования приведенного ниже кода в принятом ответе:
public interface IParent<out TChild>
where TChild : IChild
{
ICollection<TChild> Children { get; set; }
и он стал
public interface IParent
{
IEnumerable<IChild> Children { get; set; }
что намного проще.