通用存储库模式中的主键属性名称不同

时间:2018-07-03 01:24:24

标签: c# entity-framework-6 generic-programming

我在数据库中有多个表,并且每个表的主键很长,并且id列名称不是通用的,即每个表的主键列名称都不同。

例如:

  • git remote set-url heroku <your_url>的主键为“ CustomerId”
  • Customer的主键为'EmployeeId'

同样在域模型类中表示

Employee

如何引用主键来获取基于不同主键名称的值。

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); } 可以是IdCustomerId

3 个答案:

答案 0 :(得分:1)

根据您实际要执行的操作,我会考虑将类中的属性重命名为Id,然后将它们映射到模型配置中数据库中的键:

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

// ...
}

但是,如果您确实想保留这些属性,但仍然想使用通用方法来获取它们(上帝知道原因),则可能必须在某个接口上公开一个func,以便以后用于比较。

也许是这样的:

        // 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的一些问题,但这在技术上应该是可行的。否则,您将不得不深入研究表达式。

答案 1 :(得分:0)

因此,使用这种通用模式时,您的逻辑会有些瑕疵。没有属性和反射,您将无法从TEntity类型确定主ID字段的名称。一个更好的主意是让您的实体实现一个接口,然后对您的泛型施加约束。遵循这些思路

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);
}

答案 2 :(得分:0)

首先,每当看到有人通过EF实现存储库模式时,我都会感到不寒而栗

但是,除了我的内心平静。如果您具有标准的ID和标准化的ID命名,则可以使用接口

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);
}

免责声明,所有这些都未经测试,也没有错误检查