Come utilizzare le proprietà dell’interfaccia con CodeFirst

Ho le seguenti entity framework:

public interface IMyEntity { [Key] int Id { get; set; } IMyDetail MyDetail { get; set; } ICollection 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 CollectionOfReferences { get; set; } } public class MyDetail : IMyDetail { [Key] public virtual int Id { get; set; } public virtual int IntValue { get; set; } } 

Voglio usare EF CodeFirst per accedere al database e creare uno schema di database. Ma CodeFirst non consente di utilizzare i tipi di interfaccia per le relazioni tra quadro. Pertanto non crea relazioni tra MyEntity e MyDetail. Non riesco a cambiare le interfacce, quindi non posso modificare il tipo di proprietà su MyDetail anziché su IMyDetail. Ma so che il client di questo modello utilizzerà solo un’implementazione di ciascuna interfaccia.

Ho trovato una soluzione alternativa per le proprietà di tipo IMyDetail. Posso creare una proprietà di tipo MyDetail e implementare esplicitamente la proprietà dell’interfaccia:

  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; } } 

Funziona bene. Ma questa soluzione non funziona con ICollection perché non posso lanciarlo su ICollection .

Ci sono soluzioni per questo?

Una soluzione imperfetta è quella di unire semplicemente queste interfacce che si desidera mantenere nelle classi di base e scomporre gli oggetti sottostanti con sottoclassi. EF lo supporta, e se vai con Table Per Hierarchy (il valore predefinito), puoi ordinare tutti gli oggetti sottoclassi sottostanti da una proprietà condivisa usando una query LINQ regolare da EF invece di dover fare furbizia e fare cose come scrivere raw SQL o ottenere più liste in memoria e ordinare l’unione senza l’aiuto del DB, come faresti con la soluzione di Cel di interfacce e adattatori.

Si potrebbero anche utilizzare i tipi di interfacce figlio / genitore come generici, in modo che quando l’implementatore utilizzi classi concrete nel Db possano utilizzare principalmente le interfacce, ma dire a EF di utilizzare classi concrete:

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

Qualcuno potrebbe creare le loro classi Db come:

 public class Parent : IParent . . . 

Ma ancora li usano come:

 IParent parents = db.Parents.Include(p => p.Children).ToArray(); 

Poiché il generico è marcato, il generico è covariante e quindi può prendere qualsiasi cosa che soddisfi le restrizioni del generico, incluso il suddetto cast sopra l’albero dei tipi all’interfaccia IChild.

Detto questo, se vuoi veramente mantenere le interfacce, la risposta giusta è probabilmente quella di utilizzare NHibernate: come mappare un’interfaccia in nhibernate?

E alcuni programmatori consigliano di mantenere le interfacce su quadro in qualsiasi ORM limitato ad alcune proprietà condivise, o di abusare del rischio: Programmazione delle interfacce durante l’associazione con Fluent NHibernate

Una soluzione alternativa consiste nel creare un’implementazione speciale per ogni interfaccia che si desidera utilizzare con Entity Framework, utilizzando il modello dell’adattatore:

Wrapper per interfaccia

 // 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 } 

Salvataggio e caricamento di esempio

  // Repositor is your DbContext public void SubmitDomain(ISecondLevelDomain secondLevelDomain) { Repositor.SecondLevelDomainReps.Add(new SecondLevelDomainRep(secondLevelDomain)); Repositor.SaveChanges(); } public IList RetrieveDomains() { return Repositor.SecondLevelDomainReps.Select(i => i.Adaptee).ToList(); } 

Utilizzo di Proprietà di navigazione / Chiavi esterne / Mappature figlio padre

Per interfacce / classi più complesse, è ansible ottenere InvalidOperationException – vedere Modifiche in conflitto con la prima chiave esterna del codice in Entity Framework per un’implementazione che funziona con tali gerarchie di oggetti

Dopo diverse notti insonni credo di aver trovato una soluzione a questo problema. Ho testato questo approccio (un po ‘) e sembra funzionare, ma probabilmente ha bisogno di un po’ più di occhi per farlo a pezzi e spiegare perché questo approccio potrebbe rompersi. Ho usato FluentAPI per impostare gli attributi del mio database invece di decorare le proprietà della class di entity framework. Ho rimosso l’attributo virtuale dai membri della class di quadro (preferisco utilizzare gli include espliciti invece di ricorrere al caricamento lento per le entity framework figlio). Ho anche rinominato un po ‘le classi e le proprietà di esempio in modo che sia più chiaro per me. Suppongo che tu stia cercando di esprimere una relazione uno-a-molti tra un’ quadro e i suoi dettagli. Si sta tentando di implementare interfacce per le quadro nel livello repository in modo tale che i livelli superiori siano agnostici rispetto alle classi di quadro. I livelli superiori sono solo a conoscenza delle interfacce e non delle quadro stesse …

 public interface IMyEntity { int EntityId { get; set; } //children ICollection 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 _Details; public ICollection Details { get { if (_Details == null) { return null; } return _Details.Select(x => (MyDetailEntity) x).ToList(); } set { _Details = value.Select(x => (IMyDetailEntity) x).ToList(); } } ICollection 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; } } } 

Ho avuto questo problema anche su alcuni sui miei modelli e non su altri e ho provato a utilizzare la risposta accettata. Poi ho scavato un po ‘più a fondo su ciò che era diverso sui modelli che funzionavano.

La correzione consisteva nel cambiare dall’utilizzo di ICollection per utilizzare invece IEnumerable e i problemi erano andati via, finora.

Ciò ha rimosso la necessità di utilizzare il seguente codice nella risposta accettata:

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

e divenne

 public interface IParent { IEnumerable Children { get; set; } 

che è molto più semplice.

Ho avuto lo stesso problema e ho trovato una soluzione proprio come Nathan, ma puoi anche fare un passo in più e avere le proprietà denominate uguali (qui Extensions e IAddress.Extensions ), definendo esplicitamente l’interfaccia:

 public interface IAddress { string Address { get; set; } IEnumerable 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 Extensions { get; set; } [NotMapped] IEnumerable IAddress.Extensions { get { return Extensions; } set { Extensions = value as IEnumerable; } } } 

Codice Prima ignora la proprietà dell’interfaccia e utilizza la class concreta, mentre è ancora ansible accedere a questa class come interfaccia di IAddress .