如何使用Code First的接口属性

时间:2012-03-21 13:12:10

标签: entity-framework

我有以下实体:

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类型的属性并显式实现interface的属性:

    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>

对此有什么解决方案吗?

7 个答案:

答案 0 :(得分:13)

一个不完美的解决方案是将要保留的这些接口合并到基类中,并使用子类分解底层对象。 EF确实支持这一点,如果你使用Table Per Hierarchy(默认),你可以使用来自EF的常规LINQ查询通过共享属性对所有底层子类对象进行排序,而不必变得狡猾并执行诸如write raw之类的操作SQL或将多个列表放入内存并在没有DB帮助的情况下对联合进行排序,就像使用Cel的接口和适配器解决方案一样。

您还可以将子/父类型的接口作为泛型,这样当实现者在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();

因为通用标记为outthe generic is covariant因此可以采取任何符合通用限制的内容,包括上面的类型树,将其转换为IChild接口。

那就是说,如果你真的想要坚持接口,那么正确的答案可能是使用NHibernate: How to map an interface in nhibernate?

有些程序员建议您将任何ORM中的实体接口限制为少数共享属性,否则可能会导致滥用: Programming to interfaces while mapping with Fluent NHibernate

答案 1 :(得分:5)

解决方法是使用适配器模式为要与Entity Framework一起使用的每个接口创建一个特殊实现:

每个接口的包装

// 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 - 有关适用于此类对象层次结构的实现,请参阅Conflicting changes with code first foreign key in Entity Framework

答案 2 :(得分:4)

经过几个不眠之夜,我相信我找到了解决这个问题的办法。我已经测试了这种方法(一点点)它似乎有效,但它可能需要更多的眼球来撕开它并解释为什么这种方法可能会破坏。我使用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;
        }
    }
}

答案 3 :(得分:1)

我对我的模特上的某些内容也有这个问题而不是其他问题,并尝试使用接受的答案。然后我深入挖掘了有效模型的不同之处。

修复是从使用ICollection改为使用IEnumerable而且问题消失了,到目前为止。

这消除了在接受的答案中使用以下代码的需要:

public interface IParent<out TChild>
where TChild : IChild
{
ICollection<TChild> Children { get; set; }       

它变成了

public interface IParent
{
IEnumerable<IChild> Children { get; set; } 

这简单得多。

答案 4 :(得分:1)

我遇到了同样的问题并找到了像Nathan一样的解决方案,但是您甚至可以更进一步,并且具有相同的属性(此处ExtensionsIAddress.Extensions),明确定义接口:

public interface IAddress
{
    string Address { get; set; }
    IEnumerable<IAddressExtension> Extensions { get; set; }
}

public interface IAddressExtension
{
    string Key { get; set; }
    string Value { set; }
}

[Table("AddressExtensions")]
public class AddressExtension : IAddressExtension
{
    [Key]
    public string Id { get; set; }
    public string Key { get; set; }
    public string Value { get; set; }
}

[Table("Addresses")]
public class Address : IAddress
{
    [Key]
    public string Id { get; set; }
    public string Address { get; set; }

    public IEnumerable<AddressExtension> Extensions { get; set; }

    [NotMapped]
    IEnumerable<IAddressExtension> IAddress.Extensions
    {
        get { return Extensions; }
        set { Extensions = value as IEnumerable<AddressExtension>; }
    }
}

Code First忽略interface-property并使用具体类,同时您仍然可以将此类作为IAddress的接口访问。

答案 5 :(得分:1)

如果您确实需要使用接口提供的抽象,请考虑向您的应用程序添加域层。域层旨在表示没有持久性逻辑负担的实体,这将导致更简洁,更可扩展的体系结构。 (目前尚不清楚这是OP的目标,但似乎是针对其他在其他地方讨论过相同问题的人。)这可能是唯一不像其他解决方案那样引入非直觉约束的解决方案(显式接口实现,类型铸造问题...)如果走这条路线,您甚至可能甚至不需要接口-域类就足够了。

就类/命名空间结构而言,最终结果可能如下所示:

namespace Domain.Entities  // not EF
    class MyDomainEntity
namespace DataAccess.Entities  // EF entities
    class MyDataAccessEntity // no relation to MyDomainEntity
namespace DataAccess.Entities.Mappers
    class MyDataAccessEntityMapper // responsible for mapping MyDataAccessEntity to and from MyDomainEntity

诚然,这种方法需要做很多工作。您将需要2组实体(一组用于域,一组用于持久性)和类在域和持久性实体之间进行映射。因此,只有在有充分理由的情况下,这种方法才值得。否则,对于小型应用程序和持久层不太可能更改的应用程序,如果继续使用EF实体,则工作量和混乱程度可能会减少。

但是,如果您走这条路线,那么您将发现在应用程序中使用域(非EF)实体变得更加容易,而将域逻辑排除在EF实体之外也使得EF模型也更易于使用。 / p>

答案 6 :(得分:0)

我有一个类似的案例,我以这种方式解决了:

ON DELETE CASCADE