我有以下实体:
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; }
}
Run Code Online (Sandbox Code Playgroud)
我想使用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;
}
}
Run Code Online (Sandbox Code Playgroud)
它工作正常.但是这个解决方案无效,ICollection<IMyDetail>因为我无法将其投射到ICollection<MyDetail>.
这有什么解决方案吗?
Chr*_*ini 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; }
Run Code Online (Sandbox Code Playgroud)
有人可以创建他们的Db类,如:
public class Parent : IParent<Child>
. . .
Run Code Online (Sandbox Code Playgroud)
但仍然使用它们像:
IParent<IChild> parents = db.Parents.Include(p => p.Children).ToArray();
Run Code Online (Sandbox Code Playgroud)
因为泛型是标记的out,所以泛型是协变的,因此可以采取任何满足泛型限制的内容,包括上面将类型树转换为IChild接口.
也就是说,如果你真的想坚持接口,那么正确的答案可能是使用NHibernate: 如何在nhibernate中映射接口?
一些程序员建议您将任何ORM中的实体接口限制为少数共享属性,否则可能会导致误操作: 使用Fluent NHibernate进行映射时编程到接口
如果您确实需要使用接口提供的抽象,那么请考虑向您的应用程序添加域层。领域层旨在表示实体,而无需承担持久性逻辑的负担,这会带来更清晰、更可扩展的架构。(尚不清楚这是OP的目标,但似乎是为了在其他地方讨论过相同问题的其他人。)这可能是唯一不会像其他解决方案那样引入不直观约束的解决方案(显式接口实现,类型转换问题...)如果您走这条路,您可能甚至不需要接口——域类应该足够了。
从类/命名空间结构来看,最终结果可能如下所示:
namespace Domain.Entities // domain layer (not EF)
class MyDomainEntity
namespace DataAccess.Entities // peristence/EF entities
class MyDataAccessEntity // no relation to MyDomainEntity
namespace DataAccess.Entities.Mappers
class MyDataAccessEntityMapper // responsible for mapping MyDataAccessEntity to and from MyDomainEntity
Run Code Online (Sandbox Code Playgroud)
诚然,这种方法需要做更多的工作。您将需要 2 组实体(1 组用于域,1 组用于持久性)和类来在域和持久性/EF 实体之间进行映射。EF 实体仅在数据访问层中使用,而域实体将在应用程序的更高级别中使用。这种方法通常仅对于大型应用程序或有令人信服的理由才值得。否则,对于小型应用程序和持久层不太可能更改的应用程序,如果继续使用 EF 实体,可能会减少工作量和混乱。
但是,如果您确实走这条路线,那么您会发现在应用程序中使用域(非 EF)实体变得更加容易,并且将域逻辑保留在 EF 实体之外也使 EF 模型更易于使用。
我遇到了同样的问题,并找到了一个像 Nathan 一样的解决方案,但您甚至可以更进一步,通过显式定义接口,将属性命名为相同(此处Extensions和IAddress.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>; }
}
}
Run Code Online (Sandbox Code Playgroud)
Code First 忽略了 interface-property 并使用了具体类,而您仍然可以将此类作为IAddress.
解决方法是使用适配器模式为要与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
}
Run Code Online (Sandbox Code Playgroud)
// 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();
}
Run Code Online (Sandbox Code Playgroud)
对于更复杂的接口/类,您可能会获得InvalidOperationException - 请参阅与Entity Framework中的代码第一个外键的冲突更改,以获得适用于此类对象层次结构的实现
小智 5
经过几个不眠之夜,我相信我已经找到了解决这个问题的方法。我已经测试了这种方法(一点点)并且它似乎有效,但它可能需要更多的眼球才能将其撕开并解释为什么这种方法可能会失败。我使用 FluentAPI 来设置我的数据库属性,而不是修饰实体类属性。我已经从实体类成员中删除了 virtual 属性(我更喜欢使用显式包含而不是回退到子实体的延迟加载)。我还稍微重命名了示例类和属性,以便我更清楚。我假设您正在尝试表达实体与其详细信息之间的一对多关系。您正在尝试为存储库层中的实体实现接口,以便上层与实体类无关。
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
34647 次 |
| 最近记录: |