Połączenie gotowej bazy z aplikacja MVC

0

Witam,

Mam taki problem:

Posiadam gotową bazę danych z tabelami oraz relacjami miedzy nimi
Posiadam tez aplikacje która ma swoje klasy w modelu z odpowiednimi atrybutami do walidacji odpowiadającej każdej tabeli w bazie

Teraz mój problem polega na tym jak zmapować gotowe tabele w bazie danych na gotowe klasy uwzględniając przy tym tez relacje?
Czy powinienem użyć techniki code-first?

Mój projekt wygląda następująco:

SubjectModel.cs

public class SubjectModel
    {
        [HiddenInput(DisplayValue=false)]
        public int Subject_Id { get; set; }

        [Required]
        [Display(Name = "Nazwa")]
        [StringLength(30)]
        public string Name { get; set; }

        [Required]
        [Display(Name = "Skrót")]
        [StringLength(10)]
        public string ShortName { get; set; }

        [Display(Name = "Czy przedmiot ma prowadzić opiekun grupy?")]
        public bool GroupLeader { get; set; }
    }

SubjectLINQ_To_EF.cs

public class SubjectsLINQ_To_EF : ISubjects
    {
        private Entities context;
        private int UserId;

        public SubjectsLINQ_To_EF()
        {
            this.context = new Entities();
            this.UserId = WebSecurity.CurrentUserId;
        }

        public List<SubjectModel> GetSubjects(int TypeSchool)
        {
            var guery = (from o in context.Subjects
                         where o.UserId == UserId && o.TypeSchool == TypeSchool
                         select new SubjectModel
                         {
                             Subject_Id = o.Subject_Id,
                             Name = o.Name,
                             ShortName = o.ShortName,
                             GroupLeader = o.GroupLeader
                         }).ToList();
            
            return guery;
        }

        public SubjectModel GetSubjectById(int Subject_Id)
        {
            try
            {
                var guery = (from o in context.Subjects
                             where o.Subject_Id == Subject_Id
                             select new SubjectModel
                             {
                                 Subject_Id = o.Subject_Id,
                                 Name = o.Name,
                                 ShortName = o.ShortName,
                                 GroupLeader = o.GroupLeader
                             }).Single();

                return guery;
            }
            catch
            {
                return null;
            }
        }

        public void AddSubject(int TypeSchool, SubjectModel obj)
        {
            Subjects o = new Subjects
            {
                UserId = UserId,
                TypeSchool = TypeSchool,
                Name = obj.Name,
                ShortName = obj.ShortName,
                GroupLeader = obj.GroupLeader
            };

            context.Subjects.InsertOnSubmit(o);
            context.SubmitChanges();
        }

        public void EditSubject(SubjectModel obj)
        {
            var o = context.Subjects.Find(obj.Subject_Id);

            if (o != null)
            {
                o.Name = obj.Name;
                o.ShortName = obj.ShortName;
                o.GroupLeader = obj.GroupLeader;

                context.SubmitChanges();
            }
        }

        public void DeleteSubject(int Subject_Id)
        {
            var obj = context.Subjects.Find(Subject_Id);

            if (obj != null)
            {
                context.Subjects.DeleteOnSubmit(o);
                context.SubmitChanges();
            }
        }
    }

Dodam ze aplikacja posiada element Entity Data Model do którego odwołuje się w celu uzyskania informacji z bazy.
Czy odpowiednio zabrałem się za stworzenie tego połączenia?
Co zrobić gdy analogicznie stworze klasę Teacher i ona jest powiązana relacja z tabela Subject więc chciałbym aby tabela teacher zawierała listę Subjects?
Chodzi mi o to czy dobrze robię pobierając dane z bazy do wyświetlenia za każdym razem tworze klasę Subject?
Zauważyłem ze Entity Data Model tworzy własny DbContext zawierający właśnie te tabele i wygenerowane już klasy z tymi polami no ale korzystając z tych klas to co wtedy gdy będę potrzebował atrybutów we walidacji? Dzięki temu w klasie SubjectLINQ_To_EF.cs zamiast SubjectModel mógłbym zwracać klasę wygenerowana w Entity Data Model która już posiada powiązane ze sobą relacje lecz własnie boli mnie ten problem walidacji gdy będę chciał np dodać nowy element do bazy który kontrolują atrybuty postawione przy polach w klasie, czy powinienem korzystać przy zczytywaniu z bazy to EDM a przy dodawaniu i edycji to za pomocą klasy SubjectModel i ręcznie mapować tak jak jest w klasie SubjectLINQ_To_EF.cs?
Oczywiście chce oddzielić w ten sposób kontrolery od działania bazy danych wykonując zapytania w tej drugiej klasie.

Na temat EF w internecie jest wiele metod jednak ciężko mi do poukładać w jedną całość pasująca odpowiednio do mojej aplikacji, może ja za bardzo kombinuje jednak potrzebuje pomocnej ręki która poukłada moje teorie w jedna całość.

2

Kompletnie nie rozumiem tego kodu - z tego, co widzę, to używasz chyba LINQ to SQL, a nie EF, klasa SubjectsLINQ_To_EF to jakieś niby repozytorium z błędogennymi metodami (zwracają null), a nazwa ISubjects zupełnie nic nie mówi. No i szkoda, że tworzysz obiekty ręcznie, a nie używasz jakiegoś IoC.

Ogólnie powinieneś mieć dwa (co najmniej, bo w bardziej skomplikowanej aplikacji będzie ich więcej) oddzielne zestawy klas:

  1. Związane z interfejsem użytkownika, zawierające wszystkie atrybuty walidacyjne, itd. Takie klasy nazywa się view modelami.
  2. Zmapowane do tabel bazy danych. To są encje.

Teoretycznie można mieć w aplikacji klasy do wszystkiego, tj. będące jednocześnie encjami i view modelami, ale praktyka pokazuje, że jest to bardzo niewygodne do rozwijania i utrzymania.

Ja działanie aplikacji MVC od góry do dołu realizuję tak:

  1. Metoda kontrolera obsługująca post dostaje view model "od użytkownika".
  2. Ta metoda wywołuje metodę z serwisu. Serwis to klasa, która faktycznie wykonuje jakąś logikę przetwarzania danych. (Można powiedzieć, że każdy kontroler ma co najmniej jeden swój serwis, ale też mogą istnieć serwisy niepowiązane z kontrolerami, a jedynie pomocnicze dla innych serwisów.)
  3. Metoda serwisu zna zarówno view model, jak i encję. W jakiś sposób przetwarza ten view model, a następnie mapuje go do encji (jednej lub wielu, bo czasem i tak trzeba). Mapowania właściwości encji na view model można dokonać ręcznie (jak Ty w kodzie powyżej) albo jakąś automatyczną biblioteką, np. AutoMapper albo ValueInjecter.
  4. Na koniec metoda serwisu za pomocą bezpośrednio kontekstu EF albo jakiejś klasy pośredniczącej zapisuje encję do bazy.
0

Hm, no to teraz już całkiem nie rozumiem, mógłbyś mi pokazać na malutkim przykładzie z jedna relacja, najprostszym jaki się da, ciężko mi to ogarnąć tak słowna logika? Oczywiście z kontenerów stosuję Ninject jednak tylko w miejscu gdy kontroler korzysta z klasy, a wygląda to tak:

 private IProfile rep;

        public ProfileController(IProfile obj)
        {
            this.rep = obj;
        }

Dokładnie próbowałem stworzyć repozytorium tak jak robi się w Linq To Sql, naoglądałem się i naczytałem wiele sposobów jednak żaden nie pokazuje jak posklejać to w jedną całość, czyli:

  • utworzyć ViewModel dla danej zwrotnej do POST
  • jednocześnie tworząc relacje które odczytują dane do jakichś encji (jeśli dobrze rozumiem)
  • mającą dla kontrolera swój serwis realizujący logikę bazy danych (ja myślałem ze tworząc klasę Linq_TO_EF to wtedy to sie ma wykonywać)
  • działającą w mvc.

Czy mapowanie "Mapowania właściwości encji na view model" nie robi już Entity Data Model ADO.NET?

Mógłbyś podrzucić jeśli masz na oku jakiś film pokazujący wykonanie tego co mówisz bądź jakiś artykuł będę naprawdę bardzo wdzięczny, ponieważ chcę to wykonać dobrze robiłem na swój sposób i teraz chce to poprawić bo widzę że tu robię coś nie tak?

0

Ponizej najprostszy przykład.

//view model (klasa tylko zawierająca to, czego potrzebuje widok + opcjonalnie atrybuty do walidacji. Ja osobiscie stsosuje sobne klasy do walidacji (polecam FluentValidator)

public class AddProductViewModel {
	public string Name {get;set;}
	public decimal Price {get;set;}
	public int TaxCategoryId {get;set;}
	public List<SelectListItem> TaxCategories {get;set;}
}

//Encja (może posiadac logike biznesowa)
public class Product  {
	public int Id {get;set;}
	public string Name {get;set;}
	public decimal Price {get;set;}
	public TaxCategory TaxCategory {get;set:}
	public decimal GetGrossPrice() {
		return Math.Round(((this.TaxCategory.Value + 100) / 100) * this.Price,2);
	}
	public class Product() {}
	public class Product(string Name, decimal Price, TaxCategory taxCategory) {
		this.Name=Name;
		this.Price=Price;
		this.TaxCategory=taxCategory;
	}	  

}


public ProductController(IProductService productService, ITaxCategoryService TaxCategoryService) {
           this.productService=productService;
           this.TaxCategoryService=TaxCategoryService;
}

[HttpGet]
public ActionResult NewProduct() {
	IEnumerable<TaxCategory> taxCategories = TaxCategoryService.GetAll();
	var model = new AddProductViewModel();
	model.TaxCategories = new List<SelectListItem>();
	foreach(TaxCategory item in taxCategories) {
		model.TaxCategories.Add(new SelectListItem { Text = item.Name, Value = item.Id.ToString() });
	}
	return View(model);
}
[HttpPost]
public ActionResult AddProduct(AddProductViewModel model) {
           if(ModelState.isValid) {
	           TaxCategory taxCategory =  TaxCategoryService.GetById(model.TaxCategoryId);
            	Product product = new Product(model.Name,model.Price,taxCategory);
	           productService.Add(product);
            }
            else  {
 	....
             }
}

Klasy serwisowe maja tylko dostep do repozytoriow i odpowiadaja one za pobieranie/zapisywanie z/do repozytoriow, zatwierdzanie zmian, obsluge uprawnien, walidacje elementow kluczowych dla warstwy aplikacji i w ograniczonym zakresie za logike biznesowa (a raczej powinny koordynowac wykonanie zbioru instrukcji na obiektach posiadajacych logike biznesowa).

0

no ok ale gdzie tu jakiś dbcontext mapujacy tabele i relacje z bazy danych ? jak stworzyć model który będzie miał dostęp do fizycznej bazy w tym wszystkim oczywiście wykorzystują ef

gdzie stosowac zapytania linq

0

Dla uproszczenia, możesz korzystać bezpośrednio w serwisach z dbContextu. Przykład:

    public interface IDbContext
    {
        IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;
        int SaveChanges();
        void Dispose();
    }
    public class EfDbContext : System.Data.Entity.DbContext , IDbContext
    {
        public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
        {
            return base.Set<TEntity>();
        }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
             modelBuilder.Configurations.Add<Product>(new ProductMap());
        }
    }
    public class ProductService : IProductService {
            IDbContext dbcontext;
            public ProductService(IDbContext dbcontext) {
                this.dbcontext=dbcontext;
            }
            public Product GetById(int Id) {
                return this.dbcontext.Set<Product>().FirstOrDefault(d=>d.Id==Id);
            }
            public void AddProduct(Product product) 
            {
                 this.dbcontext.Set<Product>().Add(product);
                 this.dbcontext.SaveChanges();
            }
    } 
}

W CodeFirst
w metodzie OnModelCreating definiujesz powiązania encja (Product) -> mapowanie (ProductMap). Poczytaj o fluent api w code first.
Poniżej najprostsze mapowanie z jedną relacją.

    public partial class ProductMap : EntityTypeConfiguration<Product>
    {
        public ProductMap()
        {
            this.ToTable("Product");
            this.HasKey(p => p.Id);
            this.HasRequired(d=>d.TaxCategory).WithMany(s=>s.Products).HasForeignKey(e=>e.TaxCategoryId); //Przykładowe mapowanie relacji
        }
    }
1
jawka7 napisał(a):

Oczywiście z kontenerów stosuję Ninject jednak tylko w miejscu gdy kontroler korzysta z klasy, a wygląda to tak:

Więc DbContext powinieneś wstrzykiwać również przez Ninjecta.

Dokładnie próbowałem stworzyć repozytorium tak jak robi się w Linq To Sql

Nie wiem, co masz na myśli, ale olej LINQ to SQL. To porzucona technologia, której nikt już nie używa. Teraz się stosuje Entity Framework (albo konkurencyjne rozwiązania).

  • utworzyć ViewModel dla danej zwrotnej do POST

To już chyba masz - Twoja klasa z atrybutami walidacyjnymi to właśnie ViewModel.

  • jednocześnie tworząc relacje które odczytują dane do jakichś encji (jeśli dobrze rozumiem)

Relacja to raczej w bazach danych... Co masz na myśli?

  • mającą dla kontrolera swój serwis realizujący logikę bazy danych (ja myślałem ze tworząc klasę Linq_TO_EF to wtedy to sie ma wykonywać)

Powiedzmy... Ale czemu jakiś Linq to EF?

Czy mapowanie "Mapowania właściwości encji na view model" nie robi już Entity Data Model ADO.NET?

Nie, Entity Framework to ORM, on mapuje tabele z bazy do klas aplikacji.

Mógłbyś podrzucić jeśli masz na oku jakiś film pokazujący wykonanie tego co mówisz bądź jakiś artykuł będę naprawdę bardzo wdzięczny, ponieważ chcę to wykonać dobrze robiłem na swój sposób i teraz chce to poprawić bo widzę że tu robię coś nie tak?

Nie znam filmów, artykułu też jeszcze nie napisałem. ;)

Jakiś przykład może uda mi się wrzucić wieczorem.

model.TaxCategories = new List<SelectListItem>();
foreach(TaxCategory item in taxCategories) {
    model.TaxCategories.Add(new SelectListItem { Text = item.Name, Value = item.Id.ToString() });
}

Jest coś takiego jak LINQ:

model.TaxCategories = taxCategories.Select(q => new SelectListItem { Text = q.Name, Value = q.Id.ToString() }).ToList();

@micc, i czemu piszesz o code first, skoro autor już ma bazę?

0

Zboczenie, pewnie dlatego, że nie trawię wszelkich generatorów od Microsoftu i nawet jak mam gotową bazę to robię ręcznie mapowania.

0

No już mniej więcej czaje lecz przyznam ciężko mi się przestawić na ten tok myślenia,
no nic jakoś to przetrawie i zobaczymy co z tego wyjdzie nie chce już cię męczyć lecz każdy "DOBRY" przykład jest dla mnie jak sztabka złota i tak twoja pomoc dała mi dużo do myślenia i jestem wdzięczny że miałeś czas na rozpisanie mi tego w szczegółach co wiem że też zabrało ci troszkę czasu.
Pisze właśnie prace dyplomową a nasza uczelnia niestety dużo wymaga i w niewielkim czasie muszę ogarniać EF z czym nie miałem wcześniej do czynienia,
na dodatek w ostatnim semestrze dali nam MVC i lekko sugerują że w tym powinna być nasza praca,
tylko szkoda że do końca zostało kilka miesięcy i na dodatek chcą dobrze wykonanej pracy, troszkę nierealne że mamy pisać w czymś czego nie do końca umiemy ;/
A ja już z natury nie lubię pisać czegoś co działa a ja nie wiem jak i czy dobrze, najpierw muszę poznać technologie od podszewki i jak coś mam pisać to muszę wiedzieć co i po co.

Jeszcze raz dzięki,
Podrzuć adres wysyłam flaszkę ;)

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