Имя атрибута первичного ключа отличается в универсальном шаблоне репозитория - PullRequest
0 голосов
/ 03 июля 2018

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

Например:

  • Таблица Customer имеет первичный ключ как «CustomerId»
  • Таблица Employee имеет первичный ключ как «EmployeeId»

И это же будет представлено в классе модели предметной области

class Customer { public long CustomerId { get; set; } 

class Employee { public long EmployeeId { get; set; }

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

public async Task<TEntity> GetById(int id)
{
    return await _dbContext.Set<TEntity>()
                .AsNoTracking()
                .FirstOrDefaultAsync(e => e.**Id** == id);
}

Id может быть CustomerId или EmployeeId

Ответы [ 3 ]

0 голосов
/ 03 июля 2018

Во-первых, я получаю дрожь всякий раз, когда вижу, как кто-то реализует шаблон репозитория поверх EF

Тем не менее, мое спокойствие в стороне. Если у вас стандартное и нормализованное именование идентификаторов, вы можете использовать интерфейс

public interface IId
{
    public long Id {get;set;}        
}

// ...

class Customer : IId { public long ID{get;set;} ...}
class Employee : IId { public long ID{get;set;} ...}

public async Task<TEntity> GetById<TEntity>(int id) where TEntity : IId
{
    return await _dbContext.Set<TEntity>()
                .AsNoTracking()
                .FirstOrDefaultAsync(e => e.Id == id);
}

Или вы можете использовать выражение

public static async Task<T> GetById<T>(this IQueryable<T> entities, Expression<Func<T, long>> propertySelector, long id)
{
   var member = propertySelector.Body as MemberExpression;

   var property = member.Member as PropertyInfo;
   var parameter = Expression.Parameter(typeof(T));

   var expression = Expression.Lambda<Func<T, bool>>(Expression.Equal(Expression.Constant(id), Expression.Property(parameter, property)), parameter);

   return await entities.FirstOrDefaultAsync(expression);
}

Отказ от ответственности , все это полностью не проверено, и нет проверки ошибок

0 голосов
/ 03 июля 2018

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

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<MyEntityType>().Property(k => k.Id).HasColumnName("CustomerId");

// ...
}

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

Возможно, что-то вроде этого:

        // An interface that requires a func to match it's id
        public interface IExpressionKeyEntity
        {
            // A func that takes an int and returns true if it's a match
            // against the entity id
            Func<int, bool> HasMatchingId { get; }
        }

С самой простой реализацией:

        // A simple customer class that implements the interface
        public class Customer : IExpressionKeyEntity
        {
            // An id property that we don't know anything about from the outside
            public int CustomerId { get; set; }

            // A func that takes an id and returns true if it's a match against this entities id
            public Func<int, bool> HasMatchingId => comparisonEntityId => comparisonEntityId == CustomerId;
        }

А затем используйте его с некоторыми незначительными изменениями в методе GetById:

            // GetById now requires an IExpressionKeyEntity to be retrieved
            public async Task<TEntity> GetById<TEntity>(int id)
                where TEntity : IExpressionKeyEntity
            {
                // Return the first match based on our func
                return await _dbContext.Set<TEntity>()
                    .AsNoTracking()
                    .FirstOrDefault(q => q.HasMatchingId(id));
            }

У меня такое чувство, что я сталкивался с некоторыми проблемами с Func и EF раньше, но технически это должно сработать. В противном случае вам придется копаться в выражениях.

0 голосов
/ 03 июля 2018

Таким образом, для использования этого общего паттерна ваша логика немного ошибочна. Вы не сможете определить имя первичного поля идентификатора из типа TEntity без атрибутов и отражения. Лучше было бы сделать так, чтобы ваши сущности реализовали интерфейс, а затем наложили ограничение на ваш универсальный объект. Что-то в этом роде

public interface IEntity {
    int id {get;set;}
}

public class Customer : IEntity {
    public int id {get; set;}
}

Тогда в вашем репозитории вы можете ограничить обобщенные типы типом IEntity. Это позволит вам просто запрашивать на основе поля id для любого типа IEntity.

where TEntity: IEntity

Тогда ваш метод работает, просто ища поле Id, которое определено в интерфейсе

public async Task<TEntity> GetById(int id)
{
    //Since TEntity is an IEntity, it is guaranteed to have an id property
    return await _dbContext.Set<TEntity>()
                .AsNoTracking()
                .FirstOrDefaultAsync(e => e.Id == id);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...