Jekas
Jekas

Reputation: 578

c# how to deal with this generic constraints?

I have three general repository that handle three base classes:

public class Entity
{
    public int Id { get; set; }
}

public class Repository
{
    public TEntity[] GetAll<TEntity>() where TEntity : Entity
    {
        return _context.Set<TEntity>.ToArray();
    }
}

public class ArchiveEntity : Entity
{
    public bool Deleted { get; set; }
}

public class ArchiveRepository
{
    public TEntity[] GetAll<TEntity>() where TEntity : ArchiveEntity
    {
        return _context.Set<TEntity>.Where(x => x.Deleted == false).ToArray();
    }
}

public class LogicalStorageEntity : ArchiveEntity
{
    public int StorageId { get; set; }
}

public class LogicalStorageRepository
{
    public int CurrentStorageId { get; set; }

    public TEntity[] GetAll<TEntity>() where TEntity : LogicalStorageEntity
    {
        return _context.Set<TEntity>
            .Where(x => x.Deleted == false)
            .Where(x => x.StorageId = CurrentStorageId)
            .ToArray();
    }
}

Is there way to have one repository that filters entities differently depending on base class? Something that looks like:

public class Entity
{
    public int Id { get; set; }
}

public class ArchiveEntity : Entity
{
    public bool Deleted { get; set; }
}

public class LogicalStorageEntity : ArchiveEntity
{
    public int StorageId { get; set; }
}

public class UniversalRepository
{
    public TEntity[] GetAll<TEntity>() where TEntity : Entity
    {
        if (typeof(TEntity) is LogicalStorageEntity)
        {
            return _context.Set<TEntity>
                .Where(x => /* how to filter by x.Deleted */)
                .Where(x => /* how to filter by x.StorageId */)
                .ToArray();
        }

        if (typeof(TEntity) is ArchiveEntity)
        {
            return _context.Set<TEntity>
                .Where(x => /* how to filter by x.Deleted */)
                .ToArray();
        }

        return _context.Set<TEntity>.ToArray();
    }
}

Edit. The quiestion is not about how to check if entity is of specific type. The real difficult part is to apply filter when you know that entity can be filtered by Deleted or some other property. Since there is limitation TEntity : Entity , you cannot access the Deleted property.

Upvotes: 2

Views: 97

Answers (5)

Trevor Pilley
Trevor Pilley

Reputation: 16423

You can, but you shouldn't.

The separate repository per entity type is the correct way to go because that way you encapsulate the entity specific logic in the repository for that entity. If you try and make a universal repository you will have to keep adding to/changing the logic in a huge method with loads of if checks.

If you want to try and promote some code re-use, you can however provide the functionality from a base repository and allow the specific repositories to specify the behaviour:

public abstract class Repository<TEntity> where TEntity : Entity
{
    protected virtual Expression<Func<TEntity, bool>> Filter { get { return null; } }

    public TEntity[] GetAll()
    {
        if (this.Filter == null)
        {
            return _context.Set<TEntity>().ToArray();
        }
        else
        {
            return _context.Set<TEntity>().Where(this.Filter).ToArray();
        }
    }
}

public class ArchiveRepository : Repository<Archive>
{
    public ArchiveRepository()
    {
        this.Filter = archive => !archive.IsDeleted;
    }
}

Using this approach, you can reduce the amount of repeated code but increase the readability and maintainability of the code base.

Upvotes: 2

Erix
Erix

Reputation: 7105

I'd use is over as, but it really doesn't matter.

        if (TEntity is LogicalStorageEntity)
        {
            return _context.Set<TEntity>
                .Where(x => /* filter by x.Deleted */)
                .Where(x => /* filter by x.StorageId */)
                .ToArray();
        }

        if (TEntity is ArchiveEntity)
        {
            return _context.Set<TEntity>
                .Where(x => /* filter by x.Deleted */)
                .ToArray();
        }

Upvotes: 0

Wutz
Wutz

Reputation: 2964

You can check that with is:

if (TEntity is LogicalStorageEntity)
    ...
if (TEntity is ArchiveEntity)
    ...

Upvotes: 0

Justin Harvey
Justin Harvey

Reputation: 14682

You could do it by casting, but I would question the usefulness of a generic method that only performs non-generic functionality.

What I mean is, if there is no common code that is executed for more than one type, then you might really just as well implement it in class specific derivations in my opinion.

Upvotes: 3

Wilbert
Wilbert

Reputation: 7419

Use 'as' casting.

LogicalStorageEntity lse = entity as LocalStorageEntity;
if (lse != null)
{
     // we know that the entity is a LogicalStorageEntity 
}

ArchiveEntity ae = entity as ArchiveEntity;
if (ae != null)
{
     // we know that the entity is an ArchiveEntity
}
// etc...

Upvotes: 0

Related Questions