2011-01-24 8 views
17

Próbowałem zbudować pewien bazowy projekt z wykorzystaniem powyższych technologii. Chciałem maksymalnej elastyczności i testowalności, więc próbowałem używać wzorców po to, aby uczynić z tego bazę dla przyszłych projektów. Wydaje się jednak, że coś jest nie tak lub cokolwiek i naprawdę potrzebuję pomocy tutaj. Mam więc dwa pytania:Aplikacja ASP.NET MVC 3 korzystająca z oprogramowania Ninject, Entity Framework 4 Code-First CTP 5, Patterns

1- Czy coś jest nie tak z moim aktualnym kodem? Czy poprawnie zastosowałem wzory? Jakieś sugestie lub zalecenia, które poprowadziłyby mnie we właściwym kierunku?

Dlaczego ten kod faktycznie łączy się z bazą danych, tworzy ją, ale nie obsługuje wstawiania, nawet jeśli wykonuję operację poprawiania? (Zobacz na końcu postu, aby uzyskać szczegółowe informacje na temat tego błędu) ** FIXED **

Wierzę, że to może również pomóc innym, ponieważ nie znalazłem wystarczająco dużo informacji, aby coś zrobić poprawnie. Jestem pewien, że wiele osób próbuje to zrobić we właściwy sposób i nie jestem pewien jak ja, jeśli to, co robię jest słuszne.

Mam dwa podmioty: Komentarz główna

KOMENTARZ

public class Comment 
{ 
[Key] 
public virtual int Id { get; set; } 

public virtual string Name { get; set; } 
public virtual string Author { get; set; } 
public virtual string Body { get; set; } 
} 

PRZEGLĄD

public class Review 
{ 
[Key] 
public virtual int Id { get; set; } 

public virtual string Name { get; set; } 
public virtual string Author { get; set; } 
public virtual string Body { get; set; } 
public virtual bool Visible { get; set; } 

public IEnumerable<Comment> Comments { get; set; } 
} 

I zbudowany repozytorium bazowy dla każdego z nich w ten sposób:

GENERIC REPOZYTORIUM

public abstract class EFRepositoryBase<T> : IRepository<T> where T : class 
{ 
private Database _database; 
private readonly IDbSet<T> _dbset; 

protected IDatabaseFactory DatabaseFactory { get; private set; } 
protected Database Database { get { return _database ?? (_database = DatabaseFactory.Get()); } } 

public EFRepositoryBase(IDatabaseFactory databaseFactory) 
{ 
    DatabaseFactory = databaseFactory; 
    _dbset = Database.Set<T>(); 
} 

public virtual void Add(T entity) 
{ 
    _dbset.Add(entity); 
} 

public virtual void Delete(T entity) 
{ 
    _dbset.Remove(entity); 
} 

public virtual T GetById(long id) 
{ 
    return _dbset.Find(id); 
} 

public virtual IEnumerable<T> All() 
{ 
    return _dbset.ToList(); 
} 
} 

Dla konkretnych operacji, używam interfejsu:

public interface IReviewRepository : IRepository<Review> { 
// Add specific review operations 
IEnumerable<Review> FindByAuthor(string author); 
} 

Więc ja dostaję działania leków generycznych z klasy abstrakcyjnej plus konkretne operacje:

public class EFReviewRepository : EFRepositoryBase<Review>, IReviewRepository 
{ 
public EFReviewRepository(IDatabaseFactory databaseFactory) 
    : base(databaseFactory) 
{ } 

public IEnumerable<Review> FindByAuthor(string author) 
{ 
    return base.Database.Reviews.Where(r => r.Author.StartsWith(author)) 
    .AsEnumerable<Review>(); 
} 
} 

Jak się zorientowaliście, używam również bazy danych facto ry będzie produkować kontekst bazy danych:

DATABASE FACTORY

public class DatabaseFactory : Disposable, IDatabaseFactory 
{ 
private Database _database; 

public Database Get() 
{ 
    return _database ?? (_database = new Database(@"AppDb")); 
} 

protected override void DisposeCore() 
{ 
    if (_database != null) 
    _database.Dispose(); 
} 
} 

jednorazowa (niektóre metody rozszerzenia ...)

public class Disposable : IDisposable 
{ 
private bool isDisposed; 

~Disposable() 
{ 
    Dispose(false); 
} 

public void Dispose() 
{ 
    Dispose(true); 
    GC.SuppressFinalize(this); 
} 
private void Dispose(bool disposing) 
{ 
    if (!isDisposed && disposing) 
    { 
    DisposeCore(); 
    } 

    isDisposed = true; 
} 

protected virtual void DisposeCore() 
{ 
} 
} 

DATABASE

public class Database : DbContext 
{ 
private IDbSet<Review> _reviews; 

public IDbSet<Review> Reviews 
{ 
    get { return _reviews ?? (_reviews = DbSet<Review>()); } 
} 

public virtual IDbSet<T> DbSet<T>() where T : class 
{ 
    return Set<T>(); 
} 

public Database(string connectionString) 
    : base(connectionString) 
{ 
    //_reviews = Reviews; 
} 

public virtual void Commit() 
{ 
    base.SaveChanges(); 
} 

/* 
protected override void OnModelCreating(ModelBuilder modelBuilder) 
{ 
    // TODO: Use Fluent API Here 
} 
*/ 
} 

I na koniec mam swoją jednostkę pracy ....

jednostka pracy

public class UnitOfWork : IUnitOfWork 
{ 
private readonly IDatabaseFactory _databaseFactory; 
private Database _database; 

public UnitOfWork(IDatabaseFactory databaseFactory) 
{ 
    _databaseFactory = databaseFactory; 
} 

protected Database Database 
{ 
    get { return _database ?? (_database = _databaseFactory.Get()); } 
} 

public void Commit() 
{ 
    Database.Commit(); 
} 
} 

ja też zbindowanych użyciu Ninject interfejsy:

NINJECT CONTROLLER FACTORY

public class NinjectControllerFactory : DefaultControllerFactory 
{ 
// A Ninject "Kernel" is the thing that can supply object instances 
private IKernel kernel = new StandardKernel(new ReviewsDemoServices()); 

// ASP.NET MVC calls this to get the controller for each request 
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) 
{ 
    if (controllerType == null) 
    return null; 
    return (IController)kernel.Get(controllerType); 
} 

private class ReviewsDemoServices : NinjectModule 
{ 
    public override void Load() 
    { 
    // Bindings... 
    Bind<IReviewRepository>().To<EFReviewRepository>(); 
    Bind<IUnitOfWork>().To<UnitOfWork>(); 
    Bind<IDatabaseFactory>().To<DatabaseFactory>(); 
    Bind<IDisposable>().To<Disposable>(); 
    } 
} 
} 

Jednak kiedy zadzwonić w konstruktorze (The domyślna akcja) ...

public class ReviewController : Controller 
    { 
     private readonly IReviewRepository _reviewRepository; 
     private readonly IUnitOfWork _unitOfWork; 

     public ReviewController(IReviewRepository postRepository, IUnitOfWork unitOfWork) 
     { 
      _reviewRepository = postRepository; 
      _unitOfWork = unitOfWork; 
     } 

     public ActionResult Index() 
     { 
      Review r = new Review { Id = 1, Name = "Test", Visible = true, Author = "a", Body = "b" }; 
      _reviewRepository.Add(r); 
      _unitOfWork.Commit(); 

      return View(_reviewRepository.All()); 
     } 

    } 

To wydaje się tworzyć bazę danych, ale nie wstawia niczego w bazie danych w EF4. Wydaje się, że może zorientowali się problem .. patrząc na obiekcie bazy danych .. stan połączenia jest zamknięta i wersja serwera wyjątek tego rodzaju:

ServerVersion = '(((System.Data.Entity.DbContext (_database)).Database.Connection).ServerVersion' threw an exception of type 'System.InvalidOperationException' 

robie właściwe rzeczy? Czy jest coś złego w tym, co zbudowałem?

Również jeśli masz zalecenia dotyczące kodu, który napisałem, byłbym zadowolony. Po prostu staram się nauczyć właściwego sposobu budowania każdego rodzaju aplikacji w MVC 3. Chcę dobry początek.

używam:

  • Entity Framework 4 z Code-First

  • ASP.NET MVC 3

  • Ninject jako DI Container

  • SQL Server Express (nie R2)

  • Visual Studio 2010 Web Express

Wielkie dzięki za pomoc!

+0

Skąd masz ten tupot za to z Entity ? Jestem zainteresowany patrzeniem na coś takiego. Widziałem podobne wzorce w skurczonej aplikacji napisanej przez Kazi Manzura Rashida @ http://shrinkr.codeplex.com/ –

+0

Myślę, że ten kod był pierwotnie z http://myfinance.codeplex.com/ – woggles

+1

Nie mam zebranych ten kod z innych postów wokół tego miejsca plus ktoś, z kim rozmawiałem, jest naprawdę ekspertem w tej dziedzinie. Miałem zamiar umieścić tutaj cały mój kod, aby pomóc innym w tym wszystkim. – Rushino

Odpowiedz

11

Eww. Ten był podstępny. Właściwie to nie wiem zbyt wiele, więc nie mogłem tego rozgryźć od razu.

Znalazłem rozwiązanie dla pytania DRUGIE, które było związane z błędem przez stwierdzenie, że ninject faktycznie strzela dwie instancje DatabaseFactory, jedna dla repozytorium i jedna dla jednostki pracy. W rzeczywistości błąd nie był problemem. Był to błąd wewnętrzny w bazie danych obiektów, ale jest normalny, ponieważ używam Entity Framework.

Prawdziwy problem polegał na tym, że Ninject wiązał dwa różne przypadki IDatabaseFactory, które doprowadziły do ​​otwarcia 2 połączeń.

Przegląd został dodany do pierwszego zestawu w _reviewRepostory, który wykorzystywał pierwszą instancję bazy danych.

Podczas wywoływania zatwierdzenia w jednostce pracy ... nic nie zapisano ze względu na fakt, że recenzja nie była w tej instancji bazy danych. W rzeczywistości jednostka pracy zwana bazą danych, która doprowadziła do utworzenia nowej instancji, ponieważ ninject wysłał jej nową instancję.

Aby to naprawić wystarczy użyć:

Bind<IDatabaseFactory>().To<DatabaseFactory>().InSingletonScope(); 

zamiast

Bind<IDatabaseFactory>().To<DatabaseFactory>(); 

A teraz całą pracę systemu poprawnie!

Teraz, chciałbym kilka odpowiedzi na pierwsze pytanie, które było, jeśli coś nie tak z mojego obecnego kodu? Czy poprawnie zastosowałem wzory? Wszelkie sugestie lub zalecenia, które poprowadzą mnie we właściwym kierunku?

+9

Możesz chcieć zmienić InSingletonScope na InRequestScope (https://github.com/ninject/ninject/wiki/Object-Scopes). Lepiej nie utrzymywać stanu między żądaniami sieci. Na przykład. Z InSingletonScope myślę, że dwóch użytkowników może uzyskać to samo wystąpienie, może nie chcesz tego. Wartością domyślną jest InTransientScope: "Nowa instancja typu zostanie utworzona za każdym razem, gdy zażąda się jej.", Co spowodowało twój błąd. –

+0

czy możesz wyjaśnić, w jaki sposób pozbywasz się swoich jednostek, ponieważ nie widzę żadnej metody wewnątrz kontrolera, która wywołuje utylizację. Zobaczyłem, że istnieje klasa o nazwie Dispose, ale nie jestem w stanie dowiedzieć się, w jaki sposób ta klasa pojawia się w obrazie i zajmuje się zbieraniem. –

+0

Metoda utylizacji dotyczy tylko fabryki, – Rushino

5

Jedna mała obserwacja: poprzez swoją EFRepositoryBase i IReviewRepository mają metody, które zwracają IEnumerable<> zamiast IQueryable<>, można zapobiec kolejnych metod dodawania filtrów wyrażeń/ograniczeń lub występy albo tak na zapytania. Zamiast tego, używając IEnumerable<>, będziesz wykonywać każde kolejne filtrowanie (np. Używając metod LINQ) na pełnym zestawie wyników, zamiast pozwalać tym operacjom na wpływanie i upraszczanie instrukcji SQL, która zostanie uruchomiona przeciwko magazynowi danych.

Innymi słowy, robisz dalsze filtrowanie na poziomie serwera WWW, a nie na poziomie bazy danych, gdzie naprawdę należy, jeśli to możliwe.

Potem znowu, to może być celowe - niekiedy przy użyciu IEnumerable<> jest ważny, jeśli chcemy zapobiec rozmówców swojej funkcji z modyfikując SQL, który jest generowany, itp

+2

Ten komentarz wyjaśnił kilka pytań, które miałem na temat architektury mojego projektu. Zdarzyło mi się, że myślę, że muszę używać 'IQueryable <>' over 'IEnumerable <>', ale nie było w 100% jasne na temat rozumowania. Dziękuję Ci. – Ecnalyr