Сущность базового класса в C# для модели репозитория

Я пытаюсь стать хорошим гражданином программирования, узнав больше об инъекции зависимостей / МОК и других методах наилучшей практики. Для этого у меня есть проект, где я пытаюсь сделать правильный выбор и спроектировать все "правильным" способом, что бы это ни значило. Ninject, Moq и ASP.NET MVC поможет с тестируемостью и получением приложения "за дверью".

однако у меня есть вопрос о том, как создать базовый класс сущности для объектов, которые мое приложение состоять из. У меня есть простая библиотека классов, на которой построено веб-приложение. Эта библиотека предоставляет интерфейс IRepository, а реализация по умолчанию (та, которую использует приложение) использует Linq-to-SQL под обложками (DataContext и т. д. не подвергается воздействию веб-приложения) и просто содержит способы извлечения этих объектов. Хранилища в основном выглядит так (упрощенно):

public interface IRepository
{
    IEnumerable<T> FindAll<T>() where T : Entity
    T Get<T>(int id) where T : Entity
}

реализация по умолчанию использует метод GetTable() в DataContext для предоставления правильные данные.

однако для этого требуется, чтобы базовый класс "Entity" имел некоторые функции. Достаточно легко получить мои объекты для наследования от него, создав частичный класс с тем же именем, что и сопоставленный объект, который дает мне Linq-to-SQL, но каков "правильный" способ сделать это?

например, интерфейс выше имеет функцию для получения сущности по ее id - все различные виды классов, производных от entity, действительно имеют поле " id " типа int (сопоставлено с первичным ключом их соответствующих таблиц), но как я могу указать это таким образом, чтобы я мог реализовать IRepository как это?

public class ConcreteRepository : IRepository
{
    private SomeDataContext db = new SomeDataContext();

    public IEnumerable<T> FindAll<T>() where T : Entity
    {
        return db.GetTable<T>().ToList();
    }

    public T Get(int id) where T : Entity
    {
        return db.GetTable<T>().Where(ent => ent.id == id).FirstOrDefault();
    }
}

Я делаю это из памяти на ПК без компилятора, поэтому простите любые ошибки, вы, надеюсь, получите мое намерение.

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

и я могу сделать абстрактное поле, или обычное поле, которое "скрыто" полем id, которое Linq-to-SQL вставляет в сгенерированные классы.

но все это похоже на обман и даже дает предупреждения компилятора.

должен ли "Entity" действительно быть "IEntity" (вместо интерфейса), и я должен попытаться определить поле id таким образом, чтобы Linq-to-SQL выполнял? Это также упростило бы указание других интерфейсов, которые необходимо реализовать реализаторам сущностей.

или "Сущность" должна быть абстрактный базовый класс с абстрактным полем id и должен ли он также реализовывать необходимые интерфейсы абстрактным способом для переопределения другими?

Я не знаю C# достаточно хорошо, чтобы увидеть элегантное решение этого, поэтому я хотел бы услышать от более опытных системных дизайнеров с некоторым опытом базового класса.

спасибо!

редактировать 10 апреля:

Я вижу, что я пропустил здесь что-то важное. Мой IRepository имеет два конкретные реализации-Один, который является "реальным" с использованием LINQ to SQL, другой-InMemoryRepository, который просто использует список на данный момент, который используется для модульного тестирования и т. д.

два добавленных решения будут работать для одной из этих ситуаций, а не для другой. Поэтому, если возможно, мне нужен способ определить, что все, что наследуется от Entity, будет иметь свойство "id", и что это будет работать с LINQ to SQL DataContext без "обмана".

6 ответов


если вы используете " Id " в качестве первичных ключей во всех таблицах, чтобы все сгенерированные классы имели публичное свойство, такое как:

public int Id {}

затем создайте интерфейс

public interface IIdentifiable {
    Id { get; }
}

затем самая утомительная часть: (, для всех сущностей создайте частичный класс и сделайте его реализацию IIdentifiable.

класс репозитория может выглядеть следующим образом:

public class Repository<T> : IRepository where T : IIdentifiable {
}

и следующий код будет работать:

db.GetTable<T>().SingleOrDefault(ent => ent.Id.Equals(id));

если вы не используете созданные классы а сделать свое, еще проще с этой точки зрения.

EDIT:
Вместо ent => ent.Id = = id использовать ent => ent.Id.Равно (id). Только что протестировано, ниже приведен полный рабочий пример:

public interface IIdentifiable {
    int Id { get; }
}

public class Repository<T> where T : class, IIdentifiable {
    TestDataContext dataContext = new TestDataContext();

    public T GetById(int id) {
        T t = dataContext.GetTable<T>().SingleOrDefault(elem => elem.Id.Equals(id));
        return t;
    }
}

public partial class Item : IIdentifiable {
}

class Program {
    static void Main(string[] args) {
        Repository<Item> itemRepository = new Repository<Item>();

        Item item = itemRepository.GetById(1);

        Console.WriteLine(item.Text);
    }
}

"FindAll" - плохая идея и заставит его получить все данные. Возвращение IQueryable<T> было бы лучше, но все же не идеально, ИМО.

поиск по ID-см. здесь разделяемый ответ; это использует метамодель LINQ-to-SQL для поиска первичного ключа, так что вам не нужно. Он также работает как для атрибутивных, так и для ресурсных моделей.

Я бы не стал форсировать базовый класс. Это было не очень популярно в Entity Framework, и это вряд ли получится любой более популярным...


у меня похожий проект, который я опиралась на магазин Роба Конери идея, где, как и предложения выше, я использую интерфейс, чтобы диктовать int ID поля:

public interface IModelObject
{
    int ID { get; set; }
}

но вместо того, чтобы заставлять мои репозитории реализовывать IRepository Я выбрал методы расширения для IQueryable<T> тип. Поэтому я использую фильтры:

public static class ModelObjectFilters
{
    /// <summary>
    /// Filters the query by ID
    /// </summary>
    /// <typeparam name="T">The IModelObject being queried</typeparam>
    /// <param name="qry">The IQueryable being extended</param>
    /// <param name="ID">The ID to filter by</param>
    /// <returns></returns>
    public static IQueryable<T> WithID<T>(this IQueryable<T> qry,
        int ID) where T : IModelObject
    {
        return qry.Where(result => result.ID == ID);
    }
}

.. таким образом я могу иметь IQueryable<T> введите вернуться из моего РЕПО и просто использовать .WithID(x) тянуть один объект.

Я тоже использую этот код с объектами LinqToSql дальше в стек, поэтому компараторы равенства преобразуются в SQL.


взгляните на следующие методы, которые были размещены Денис Троллер как ответ на мой вопрос:

public virtual T GetById(short id)
            {
                var itemParameter = Expression.Parameter(typeof(T), "item");
                var whereExpression = Expression.Lambda<Func<T, bool>>
                    (
                    Expression.Equal(
                        Expression.Property(
                            itemParameter,
                            GetPrimaryKeyName<T>()
                            ),
                        Expression.Constant(id)
                        ),
                    new[] { itemParameter }
                    );
                var table = DB.GetTable<T>();
                return table.Where(whereExpression).Single();
            }


public string GetPrimaryKeyName<T>()
            {
                var type = Mapping.GetMetaType(typeof(T));

                var PK = (from m in type.DataMembers
                          where m.IsPrimaryKey
                          select m).Single();
                return PK.Name;
            }

Вы делаете то же самое, что и мы. У нас есть общий интерфейс Persist и две реализации: одна с LinqToSql, другая для насмешек.

мы столкнулись с той же проблемой о GetById. Наше решение-все объекты наследуют от одного базового класса. Этот базовый класс имеет виртуальный метод

protected object GetId()

чтобы упростить ситуацию с составным первичным ключом, мы изменили схему БД так, чтобы каждая таблица имела столбец первичного ключа. Старый составной первичный ключ стал ограничение уникальности.

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


У вас есть действительно 2 варианта для поля Id:

  1. используйте объект для представления идентификатора в вашей сущности. (Это дает вам немного больше гибкости с точки зрения первичных ключей )

  2. просто используйте Int Id. (Это упрощает работу с вашими сущностями и становится вашим соглашением для ваших сущностей/репозиториев.

теперь о том, как реализовать свою сущность. Ваш класс может быть абстрактным. Взять посмотрите на реализацию базового класса сущности в Sharp-Architechture или изCommonLibrary.NET или дозвуковой

обычно у вас есть поля аудита и, возможно, методы проверки. Я использую CommonLibrary.NET

сущность : Метод ientity

  • Id
  • CreateDate
  • UpdateDate
  • CreateUser
  • UpdateUser
  • проверить(...)
  • функция IsValid
  • ошибки

интерфейс IEntity представляет собой комбинацию полей аудита (CreateDate, UpdateUser и т. д. ) и методов проверки сущности.