Эксперименты с репозиторием с C# .NET и Entity Framework

Я создаю приложение для отслеживания активов. Использование SQL Server 2008, C# .NET и Entity Framework. Это мой первый опыт создания репозитория, который, согласно моим исследованиям, предназначен для абстрагирования процесса доступа к данным. Я экспериментировал с несколькими дизайнами, и мне любопытно, хороши ли какие-либо из них или представляют ли какие-либо серьезные риски для разработки. Репозиторий должен поддерживать запросы активов по серийному номеру, штрих-коду или имени хоста.

Чтобы примеры были короткими, я не включаю методы обновления, удаления и вставки. Кроме того, в примерах игнорируются возможные универсальные реализации, так как универсальную реализацию всегда можно разработать позже, а прямо сейчас она только сделает примеры более запутанными. Пожалуйста, прочитайте следующие 3 дизайна и дайте мне знать, если я на правильном пути с любым из них:

Дизайн 1

Моя первая конструкция выглядела так:

public interface IAssetRepository
{
    public Asset fetchBySerialNumber(String serialNumber);
    public Asset fetchByBarcode(String barcode);
    public ICollection<Asset> fetchByHostname(String hostname);
    public Asset fetchActiveByHostname(String hostname);
}

public class AssetRepository : IAssetRepository
{
    private InventoryEntites entities;

    public Asset fetchBySerialNumber(String serialNumber)
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.SerialNumber == serialNumber
                                  select a;

        return query.FirstOrDefault();
    }

    public Asset fetchByBarcode(String barcode)
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.Barcode == barcode
                                  select a;

        return query.FirstOrDefault();
    }

    public Asset fetchActiveByHostname(String hostname)
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.Hostname == hostname && a.IsDeployed == true
                                  select a;

        return query.FirstOrDefault();
    }

    public ICollection<Asset> fetchByHostname(String hostname)
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.Hostname == hostname
                                  select a;

        return query.ToList();
    }
}

Дизайн 2

Во второй попытке я подумал, что смогу воспользоваться специальным полиморфизмом, обернув примитивные типы, используемые в качестве параметров:

public interface IAssetRepository
{
    public Asset fetch(SerialNumber serialNumber);
    public Asset fetch(Barcode barcode);
    public ICollection<Asset> fetch(Hostname hostname);
    public Asset fetchActive(Hostname hostname);
}

public class SerialNumber
{
    private String value;

    public SerialNumber(String value)
    { this.value = value; }

    public String Value
    {
        get { return this.value; }
    }
}

// Barcode and Hostname classes are similar to SerialNumber

public class AssetRepository : IAssetRepository
{
    private InventoryEntites entities;

    public Asset fetch(SerialNumber serialNumber)
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.SerialNumber == serialNumber.Value
                                  select a;

        return query.FirstOrDefault();
    }

    public Asset fetch(Barcode barcode)
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.Barcode == barcode.Value
                                  select a;

        return query.FirstOrDefault();
    }

    public ICollection<Asset> fetch(Hostname hostname)
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.Hostname == hostname.Value && a.IsDeployed == true
                                  select a;

        return query.FirstOrDefault();
    }

    public Asset fetchActive(Hostname hostname)
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.Hostname == hostname.Value
                                  select a;

        return query.ToList();
    }
}

Дизайн 3

В духе «говори, а не спрашивай» мой последний дизайн перемещает фактические запросы в классы SerialNumber, Hostname и Barcode вместо того, чтобы запрашивать у них их значения. Классы SerialNumber и т. д. теперь должны содержать ссылку на источник данных. Они, вероятно, выиграют от того, что они будут интерфейсами, чтобы они могли поддерживать разные источники данных. Я не знаю, хороший ли это дизайн, потому что у каждого из них есть отдельная ссылка на сущности. Клиенты должны создать объекты (серийный номер и т. д.) перед отправкой их в репозиторий. Поскольку у клиентов не будет ссылки на сущности, та же самая ссылка на сущности не может быть внедрена во время построения:

// Same interface as last
public interface IAssetRepository
{
    public Asset fetch(SerialNumber serialNumber);
    public Asset fetch(Barcode barcode);
    public ICollection<Asset> fetch(Hostname hostname);
    public Asset fetchActive(Hostname hostname);
}

// Could include other methods like, findStartingWith(), findContains(), etc.
public class SerialNumber
{
    private InventoryEntites entities;
    private String value;

    public SerialNumber(String value)
    { 
        this.value = value;
        this.entities = new InventoryEntities();
    }

    public Asset find()
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.SerialNumber == this.value
                                  select a;

        return query.FirstOrDefault();
    }
}

// Barcode classes is similar to SerialNumber

public class Hostname
{
    private InventoryEntites entities;
    private String value;

    public Hostname(String value)
    { 
        this.value = value;
        this.entities = new InventoryEntities();
    }

    public ICollection<Asset> find()
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.Hostname == this.value
                                  select a;

        return query.ToList();
    }

    public Asset findActive()
    {
        IQueryable<Asset> query = from a in this.entities.Assets
                                  where a.Hostname == this.value && a.IsDeployed == true
                                  select a;

        return query.FirstOrDefault();
    }
}

public class AssetRepository : IAssetRepository
{
    private InventoryEntites entities;

    public Asset fetch(SerialNumber serialNumber)
    {
        return serialNumber.find();
    }

    public Asset fetch(Barcode barcode)
    {
        return barcode.find();
    }

    public ICollection<Asset> fetch(Hostname hostname)
    {
        return hostname.find();
    }

    public Asset fetchActive(Hostname hostname)
    {
        return hostname.findActive();
    }

    // Other methods could include

    public ICollection<Asset> fetch(Location location)
    {
        return location.find();
    }

    public ICollection<Asset> fetchActive(Location location)
    {
        return location.findActive();
    }
}

Обновлять

Проведя небольшое исследование, я нашел эту статью:

MSDN: шаблоны на практике: связность и взаимосвязь

Эта цитата натолкнула меня на мысль о Дизайне 3. Возможно, эту небольшую группу классов следует объединить в дизайн, более похожий на Дизайн 2?

Операция дробовика Определенный тип изменений в системе неоднократно приводит к внесению множества мелких изменений в группу классов. Хирургия дробовика обычно подразумевает, что одна логическая идея или функция распределены по нескольким классам. Попробуйте исправить это, объединив все части кода, которые должны измениться, в один связный класс.


person TheSecretSquad    schedule 02.04.2013    source источник
comment
Без какой-либо формы IoC поддержание контекстов в каждой сущности ради наличия вспомогательных методов непосредственно вне объекта будет кропотливым делом. Для меня модель должна быть POCO, придерживаться шаблона репозитория и хранить данные вместе. Как вы решили запрашивать эту информацию (с явными методами или перегрузками методов), действительно зависит от вас, но лично я бы предпочел getX 4 get методам.   -  person Brad Christie    schedule 02.04.2013
comment
Спасибо, Брэд. Не могли бы вы уточнить, почему вы предпочитаете getX перегруженным методам get?   -  person TheSecretSquad    schedule 02.04.2013
comment
Потому что getX более подробно описывает, что делает метод, ИМХО. Видя ObjectA get(ObjectB obj), я почти задаюсь вопросом, что делает метод, тогда как ObjectA getByObjectB(ObjectB obj) уверяет меня, что он использует параметр в качестве фильтра. Работая с WCF и не всегда имея комментарии, на которые можно положиться, [небольшая] дополнительная работа того стоит, когда дело доходит до удобочитаемости позже.   -  person Brad Christie    schedule 02.04.2013


Ответы (1)


Я всегда использую дизайн №1, так как проще сказать, что делает репозиторий. Это действительно сообщает намерение.

Если вы в дизайне два выполняете какую-то проверку объектов (Barcode и т. д.), чтобы проверить идентификатор, чем я бы предпочел это. Если вы этого не сделаете, это не добавит никакой ценности, поскольку вы можете взять идентификатор hostName и создать с ним BarCode.

Третий дизайн — это больше объектов доступа к данным, смешанных с репозиториями. Это нет нет.

(примечание: соблюдайте соглашения об именах .NET. Имена методов должны быть PascalCase)

person jgauffin    schedule 05.04.2013