it-swarm.com.de

Repository-Muster und Zuordnung zwischen Domänenmodellen und Entity Framework

Meine Repositorys behandeln und bieten Persistenz für ein reichhaltiges Domänenmodell. Ich möchte die anämische Entity Framework-Datenentität nicht für meine Geschäftsebenen verfügbar machen, daher benötige ich eine Art der Zuordnung zwischen ihnen.

In den meisten Fällen erfordert die Erstellung einer Domänenmodellinstanz aus einer Datenentität die Verwendung parametrisierter Konstruktoren und Methoden (da diese reichhaltig sind). Es ist nicht so einfach wie eine Eigenschafts-/Feldübereinstimmung. AutoMapper kann für die umgekehrte Situation verwendet werden (Zuordnung zu Datenentitäten), jedoch nicht beim Erstellen von Domänenmodellen.

nten ist der Kern meines Repository-Musters.

Die Klasse EntityFrameworkRepository funktioniert mit zwei generischen Typen:

  • TDomainModel: Das Rich Domain-Modell
  • TEntityModel: Die Entity Framework-Datenentität

Zwei abstrakte Methoden sind definiert:

  • ToDataEntity(TDomainModel): In Dateneinheiten konvertieren (für Add() und Update() Methoden)
  • ToDomainModel(TEntityModel): Zum Erstellen von Domänenmodellen (für die Methode Find()).

Konkrete Implementierungen dieser Methoden würden das für das betreffende Repository erforderliche Mapping definieren.

public interface IRepository<T> where T : DomainModel
{
    T Find(int id);
    void Add(T item);
    void Update(T item);
}

public abstract class EntityFrameworkRepository<TDomainModel, TEntityModel> : IRepository<TDomainModel>
    where TDomainModel : DomainModel
    where TEntityModel : EntityModel
{
    public EntityFrameworkRepository(IUnitOfWork unitOfWork)
    {
        // ...
    }

    public virtual TDomainModel Find(int id)
    {
        var entity = context.Set<TEntityModel>().Find(id);

        return ToDomainModel(entity);
    }

    public virtual void Add(TDomainModel item)
    {
        context.Set<TEntityModel>().Add(ToDataEntity(item));
    }

    public virtual void Update(TDomainModel item)
    {
        var entity = ToDataEntity(item);

        DbEntityEntry dbEntityEntry = context.Entry<TEntityModel>(entity);

        if (dbEntityEntry.State == EntityState.Detached)
        {
            context.Set<TEntityModel>().Attach(entity);

            dbEntityEntry.State = EntityState.Modified;
        }
    }

    protected abstract TEntityModel ToDataEntity(TDomainModel domainModel);
    protected abstract TDomainModel ToDomainModel(TEntityModel dataEntity);
}

Hier ist ein grundlegendes Beispiel für eine Repository-Implementierung:

public interface ICompanyRepository : IRepository<Company>
{
    // Any specific methods could be included here
}

public class CompanyRepository : EntityFrameworkRepository<Company, CompanyTableEntity>, ICompanyRepository
{
    protected CompanyTableEntity ToDataEntity(Company domainModel)
    {
        return new CompanyTable()
        {
            Name = domainModel.Name,
            City = domainModel.City
            IsActive = domainModel.IsActive
        };
    }

    protected Company ToDomainModel(CompanyTableEntity dataEntity) 
    {
        return new Company(dataEntity.Name, dataEntity.IsActive)
        {
            City = dataEntity.City
        }
    }
}

Problem:

Ein Company kann aus vielen Departments bestehen. Wenn ich diese beim Abrufen eines CompanyRepository eifrig aus dem Company laden möchte, wo würde ich dann die Zuordnung zwischen einem Department und einem DepartmentDataEntity definieren?

Ich könnte weitere Zuordnungsmethoden in CompanyRepository bereitstellen, aber das wird bald chaotisch. Bald würde es systemweit doppelte Zuordnungsmethoden geben.

Was ist ein besserer Ansatz für das oben genannte Problem?

42
Dave New

Meine Repositorys behandeln und bieten Persistenz für ein reichhaltiges Domänenmodell. Ich möchte die anämische Entity Framework-Datenentität nicht für meine Geschäftsebenen verfügbar machen, daher benötige ich eine Art der Zuordnung zwischen ihnen.

Wenn Sie Entity Framework verwenden, kann es das Rich Domain Model selbst zuordnen.

Ich habe die ähnliche Frage "Hinweise zum Zuordnen von Entitäten zu Domänenobjekten" vor kurzem beantwortet.

Ich habe NHibernate verwendet und weiß, dass Sie in Entity Framework auch Zuordnungsregeln für Ihre POCO-Objekte aus DB-Tabellen festlegen können. Es ist ein zusätzlicher Aufwand, eine weitere Abstraktionsebene über Entity Framework-Entitäten zu entwickeln. Lassen Sie den ORM für alle Zuordnungen , Zustandsverfolgung, Arbeitseinheit und Identitätszuordnung Implementierungen usw. verantwortlich sein. Moderne ORMs wissen, wie es geht Behandeln Sie all diese Probleme.

AutoMapper kann für die umgekehrte Situation verwendet werden (Zuordnung zu Datenentitäten), jedoch nicht beim Erstellen von Domänenmodellen.

Du hast vollkommen recht.

Automapper ist nützlich, wenn eine Entität ohne zusätzliche Abhängigkeiten (z. B. Repositorys, Services usw.) einer anderen Entität zugeordnet werden kann.

... wo würde ich die Zuordnung zwischen einem Department und einem DepartmentDataEntity definieren?

Ich würde es in DepartmentRepository einfügen und die Methode IList<Department> FindByCompany(int companyId) hinzufügen, um die Abteilungen des Unternehmens abzurufen.

Ich könnte weitere Zuordnungsmethoden in CompanyRepository bereitstellen, aber das wird bald chaotisch. Bald würde es systemweit doppelte Zuordnungsmethoden geben.

Was ist ein besserer Ansatz für das oben genannte Problem?

Wenn eine Liste von Departments für eine andere Entität abgerufen werden soll, sollte DepartmentRepository eine neue Methode hinzugefügt und einfach dort verwendet werden, wo sie benötigt wird.

29
Ilya Palkin

Angenommen, Sie haben das folgende Datenzugriffsobjekt ...

public class AssetDA
{        
    public HistoryLogEntry GetHistoryRecord(int id)
    {
        HistoryLogEntry record = new HistoryLogEntry();

        using (IUnitOfWork uow = new NHUnitOfWork())
        {
            IReadOnlyRepository<HistoryLogEntry> repository = new NHRepository<HistoryLogEntry>(uow);
            record = repository.Get(id);
        }

        return record;
    }
}

dies gibt eine Datenentität für das Verlaufsprotokoll zurück. Diese Dateneinheit ist wie folgt definiert ...

public class HistoryLogEntry : IEntity
{
    public virtual int Id
    { get; set; }

    public virtual int AssetID 
    { get; set; }

    public virtual DateTime Date
    { get; set; }

    public virtual string Text
    { get; set; }

    public virtual Guid UserID
    { get; set; }

    public virtual IList<AssetHistoryDetail> Details { get; set; }
}

Sie können sehen, dass die Eigenschaft Details auf eine andere Datenentität AssetHistoryDetail verweist. Jetzt muss ich in meinem Projekt diese Datenentitäten Domänenmodellobjekten zuordnen, die in meiner Geschäftslogik verwendet werden. Für das Mapping habe ich Erweiterungsmethoden definiert ... Ich weiß, dass es ein Anti-Pattern ist, da es sprachspezifisch ist, aber das Gute ist, dass es Abhängigkeiten voneinander isoliert und aufhebt ... ja, das ist das Schöne daran. Der Mapper ist also wie folgt definiert ...

internal static class AssetPOMapper
{
    internal static HistoryEntryPO FromDataObject(this HistoryLogEntry t)
    {
        return t == null ? null :
            new HistoryEntryPO()
            {
                Id = t.Id,
                AssetID = t.AssetID,
                Date = t.Date,
                Text = t.Text,
                UserID = t.UserID,
                Details = t.Details.Select(x=>x.FromDataObject()).ToList()
            };
    }

    internal static AssetHistoryDetailPO FromDataObject(this AssetHistoryDetail t)
    {
        return t == null ? null :
            new AssetHistoryDetailPO()
            {
                Id = t.Id,
                ChangedDetail = t.ChangedDetail,
                OldValue = t.OldValue,
                NewValue = t.NewValue
            };
    }
}

und das wars auch schon. Alle Abhängigkeiten befinden sich an einem Ort. Wenn ich dann ein Datenobjekt aus der Geschäftslogikebene aufrufe, lasse ich LINQ den Rest erledigen ...

var da = new AssetDA();
var entry =  da.GetHistoryRecord(1234);
var domainModelEntry = entry.FromDataObject();

Beachten Sie, dass Sie dasselbe definieren können, um Domänenmodellobjekte Datenentitäten zuzuordnen.

5
Leo

Mit dem Entity-Framework ist es im Allgemeinen eine schlechte Idee, über alle Ebenen hinweg Entitätsmodelle in eine andere Form von Modellen (Domänenmodelle, Wertobjekte, Ansichtsmodelle usw.) zu konvertieren und umgekehrt, es sei denn, Sie tun dies nur in der Anwendungsebene Sie verlieren viele EF-Funktionen, die Sie nur durch die Entitätsobjekte erreichen können, z. B. den Verlust der Änderungsverfolgung und den Verlust von abfragbarem LINQ.

Es ist besser, die Zuordnung zwischen der Repository-Ebene und der Anwendungsebene vorzunehmen. Behalten Sie die Entitätsmodelle in der Repository-Ebene bei.

4
Ronald

Ich verwende gerne benutzerdefinierte Erweiterungsmethoden, um das Mapping zwischen Entity- und Domain-Objekten durchzuführen.

  • Sie können Erweiterungsmethoden für andere Entitäten problemlos aufrufen, wenn sie sich in einer enthaltenen Entität befinden.
  • Sie können problemlos mit Sammlungen umgehen, indem Sie IEnumerable <> -Erweiterungen erstellen.

Ein einfaches Beispiel:

public static class LevelTypeItemMapping
{
    public static LevelTypeModel ToModel(this LevelTypeItem entity)
    {
        if (entity == null) return new LevelTypeModel();

        return new LevelTypeModel
        { 
            Id = entity.Id;
            IsDataCaptureRequired = entity.IsDataCaptureRequired;
            IsSetupRequired = entity.IsSetupRequired;
            IsApprover = entity.IsApprover;
            Name = entity.Name;
        }
    }
    public static IEnumerable<LevelTypeModel> ToModel(this IEnumerable<LevelTypeItem> entities)
    {
        if (entities== null) return new List<LevelTypeModel>();

        return (from e in entities
                select e.ToModel());
    }

}

... und du benutzt sie so .....

 using (IUnitOfWork uow = new NHUnitOfWork())
        {
        IReadOnlyRepository<LevelTypeItem> repository = new NHRepository<LevelTypeItem>(uow);
        record = repository.Get(id);

        return record.ToModel();

        records = repository.GetAll(); // Return a collection from the DB
        return records.ToModel(); // Convert a collection of entities to a collection of models
        }

Nicht perfekt, aber sehr einfach zu befolgen und wiederzuverwenden.

3
Dave R

Wie schon in früheren Beiträgen gesagt. Es ist wahrscheinlich am besten, bis nach den Repositories zu warten, um das eigentliche Mapping durchzuführen. ABER ich arbeite gerne mit Auto-Mapper. Es bietet eine sehr einfache Möglichkeit, Objekte anderen Objekten zuzuordnen. Für eine gewisse Trennung von Bedenken können Sie die Zuordnungen auch in einem separaten Projekt definieren. Diese Zuordnungen sind auch generisch/typbasiert:

  1. Sie geben den Basistyp im Mapping an und definieren, wie dieser den Zieltyp ausfüllt
  2. Wo Sie das Mapping benötigen, rufen Sie einfach Mapper.Map auf (baseTypeObject, DestinationTypeObject)
  3. Automapper sollte den Rest erledigen

Dies könnte den Trick tun, wenn ich Ihre Frage richtig verstanden habe.

2
Tsasken

Ich möchte nicht manuell zuordnen, daher verwende ich für die Zuordnung http://valueinjecter.codeplex.com/

0