EntityFramework, klasa bazowa a łączenie 1:1

0

Witam,

Wykorzystuję mechanizm opisany tu:
http://atmist.com/blog/base-class-for-entity-framework-code-first/

Mam bazową klasę dla wszystkich encji, która zawiera Id, DataCreated, DataModified:

public abstract class EntityObject : IEntityObject
    {
        protected EntityObject()
        {
            this.State = EntityState.Incomplete;
            this.DateCreated = DateTime.Now;
        }

        [Key]
        public long Key { get; internal protected set; }

        public EntityState State { get; internal set; }

        public DateTime DateCreated { get; protected set; }

        public void SetCreationInfo(long userId, DateTime createdAt)
        {
            DateCreated = createdAt;
        }
    } 

Jak zaimplementować relację 1:1 ? Gdy robię to w ten sposób:

 public class User : EntityObject
    {
        public virtual UserDetails UserDetails { get; set; }
} 
public class UserDetails : EntityObject
    {
        [ForeignKey("User")]
        public long UserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DateOfBirth { get; set; }

        public User User { get; set; }
    } 

Wydaję mi się, że w klasie UserDetails ten atrybut ForeignKey("User") powinien być powieszony nad właściwością Key z klasy bazowej, ale jak to zrobić?

To wyskakuje taki błąd:
UserDetails_User_Source: : Multiplicity is not valid in Role 'UserDetails_User_Source' in relationship 'UserDetails_User'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be '*'.

0

Na początek sprawdź czy nie powinno być [ForeignKey("Key")] w adnotacji klasy UserDetails

0

The property 'Key' cannot be configured as a navigation property. The property must be a valid entity type and the property should have a non-abstract getter and setter. For collection properties the type must implement ICollection<T> where T is a valid entity type.

0
public abstract class EntityObject : IEntityObject .... 

Czy ta klasa ma być abstrakcyjna? Wywala błąd, że nie może być abstract getter i seter.

0

W żadnej klasie nie masz kolekcji

0

W klasie bazowej zmień key na Id, EF widzi różnice a w klasie usuń UserId gdyż klucz główny masz już podany w klasie bazowej. Usuń także atrybuty [Key] i [ForeignKey("Key")], EF się domyśli żeby stworzyć z:

 public User User { get; set; } 

klucz obcy.
W tabeli w bazie będzie on wyglądał tak User_UserId.

0

Niestety, wcale tego automatycznie EF nie robi, jedyne na co wpadłem to coś takiego, do połączeń 1:n i m:n używam zwykłej klasy entityObject a do 1:1 stworzyłem osobną implementacje:

 public abstract class EntityObjectOneToMany : IEntityObject
    {
        protected EntityObjectOneToMany()
        {
            this.State = EntityState.Incomplete;
            this.DateCreated = DateTime.Now;
        }

        [Key, ForeignKey("User")]
        public long Key { get; internal protected set; }

        public EntityState State { get; internal set; }

        public DateTime DateCreated { get; protected set; }

        public void SetCreationInfo(long userId, DateTime createdAt)
        {
            DateCreated = createdAt;
        }
    } 

Nie podoba mi się to rozwiązanie :(.
Encje które mają mieć 1:1 dziedziczą po tej klasie:

public class UserAddressInfo : EntityObjectOneToMany
    {
        public string City { get; set; }
        public string Street { get; set; }
        public string StreetAddress { get; set; }

        public virtual User User { get; set; }
    } 

Teraz mam taki problem niby wszystko się zapisuje ok do bazy ale leci wyjątek:
{"Violation of PRIMARY KEY constraint 'PK_dbo.UserAddressInfo'. Cannot insert duplicate key in object 'dbo.UserAddressInfo'. The duplicate key value is (1).\r\nThe statement has been terminated."}

Męczę się z tym już od długiego czasu, czy zna ktoś jakiś poradnik jak skonfigurować EF przy łączeniu tabel 1:1 aby przy użyciu klasy bazowej, która będzie zawierać Id, datę stworzenia, datę modyfikacji i inne dowolne wspólne dane dla każdej encji. To rozwiązanie pozwala stworzyć wspólne repozytorium dla wszystkich encji jak np. wyciąganie rekordu za pomocą ID stosując repozytorium generyczne

0
Grzegorion napisał(a):

Niestety, wcale tego automatycznie EF nie robi
Robi, przeczytaj sobie o konwencjach EF http://www.entityframeworktutorial.net/code-first/code-first-conventions.aspx

0

Super artykuł :) Ale wciąż coś jest nie tak:
To jest wersja poprawiona:

Klasa Entity Object:

public abstract class EntityObject : IEntityObject
    {
        protected EntityObject()
        {
            this.State = EntityState.Incomplete;
            this.DateCreated = DateTime.Now;
        }

        public long Id { get; set; }

        public EntityState State { get; set; }

        public DateTime DateCreated { get; set; }

    } 

Klasa User:

 public class User : EntityObject
    {
        public User()
        { }

        public User(string applicationUserId, string email)
        {
            SetUserData(applicationUserId, email);
        }

        public virtual UserData UserData { get; set; }

        public virtual UserDetails UserDetails { get; set; }

        public virtual UserAddressInfo UserAddressInfo { get; set; }

} 

Klasa UserDetails:

public class UserDetails : EntityObject
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DateOfBirth { get; set; }

        public virtual User User { get; set; }

    } 

Pozostałe klasy (UserAddressInfo, UserData) są dokładnie tak samo skonstruowane jak UserDetails więc ich nie pokazuje:

A to jest Fluent API:

 protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions
                        .Remove<PluralizingTableNameConvention>();

            modelBuilder.Entity<UserData>()
                        .HasKey(e => e.Id);

            modelBuilder.Entity<UserData>()
                        .HasRequired(ad => ad.User)
                        .WithOptional(s => s.UserData);

            modelBuilder.Entity<UserAddressInfo>()
                        .HasKey(e => e.Id);

            modelBuilder.Entity<UserAddressInfo>()
                        .HasRequired(ad => ad.User)
                        .WithOptional(s => s.UserAddressInfo);

            modelBuilder.Entity<UserDetails>()
                        .HasKey(e => e.Id);

            modelBuilder.Entity<UserDetails>()
                        .HasRequired(ad => ad.User)
                        .WithOptional(s => s.UserDetails);

        } 

Wszystko pięknie się stworzyło i teraz User się loguje i ma konieczność uzupełnienia danych (UserDetails i UserAddressInfo).
Kiedy zapisuję UserDetails to metoda SaveChange() przechodzi (ForeignKey User_Id został użyty jakby pierwszy raz?).

Natomiast kiedy chce zapisać UserAddressInfo to SaveChange wypieprza błąd:
Violation of PRIMARY KEY constraint 'PK_dbo.UserAddressInfo'. Cannot insert duplicate key in object 'dbo.UserAddressInfo'. The duplicate key value is (1).
The statement has been terminated.

Kiedy zamienię kolejność czyli najpierw zapisze UserAddressInfo a potem UserDetails to błąd wyskakuje dla UserDetails.
Co jest nie tak zrobione?

W SQL tabele stworzyły się tak, że:

UserDetails:

Columns:
Id(PK, FK, bigint, not null),
cała reszta...

Keys:
PK_dbo.UserDetails
FK_dbo.UserDetails_dbo.User_Id

W UserAddressInfo jest dokładnie tylko zamiast UserDetails -> UserAddressInfo itd.

Dlaczego to się wypieprza? A najlepsze, że wyjątek leci, a w bazie rekord jest zapisany!

1 użytkowników online, w tym zalogowanych: 0, gości: 1