Подтвердить что ты не робот

Как использовать свойства интерфейса с помощью 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>.

Существуют ли какие-либо решения для этого?

4b9b3361

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

что намного проще.