Domanda

I have implemented Unit of Work and Repository pattern with Entity Framework. A sample operation is shown below:

public T GetById(int id)
{
     return _context.Set<T>().Find(id);
}

So if I wanted to bring a record with id 12 and only StudentName column. I cannot do that using the above method as all columns will be pulled which I could have done using Linq like below:

Student get = context.Students
                    .Where(s => s.Id = 12)
                    .Select(s => new { StudentName = Name })
                    .SingleOrDefault();

Currently, I am returning an IQueryable from the repository like below to make above aforementioned scenario work:

public IQueryable<T> Query() 
{
    IQueryable<T> query = dbset;
    return query;
}

which makes the point of having a Repository to null anyway because I cannot restrict operations on database now. I have to query like below:

Student get = _uow.Students
                 .Query()
                 .Where(s => s.Id = 12)
                 .Select(s => new { StudentName = Name })
                 .SingleOrDefault();

Any suggestions on how to improve this situation or other opinions are required please.

È stato utile?

Soluzione

I would argue that you are worried about restricting operations at the wrong layer; the Repository. Assuming your Repositories are supposed to abstract away the details of your DB and Entity Framework, I think this is too low of a level to do what you want.

If you have a Repo per DB table, and a class per query result type (direct SQL/EF or DB View), it doesn't make sense to introduce another layer of abstraction here. It would be better to do this at the next layer up, or whatever is handling your transactional boundaries.

To demonstrate, here is a more concrete example:

Given a Student DB table:

TABLE Student
    PK Id int
    COLUMN Name string
    COLUMN SecretData string

Your StudentRepo should always return instance(s) of a Student class:

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string SecretData { get; set; }
}

Then the layer that utilizes your repo(s) should handle your transactions (potentially across multiple repos/operations) and map its results to a Domain entity. Your domain entity can include only the fields which you want to surface. You can create specialized domain entites for each purpose you need.

public class DomainStudent
{
    public int Id { get; private set; } // prevent attempts to change Ids on domain entities
    public string Name { get; set; }
}

public class DomainStudentWithSecret
{
    public int Id { get; private set; }
    public string Name { get; set; }
    public string SecretData { get; set; }
}

And to expand on why you would want to handle this kind of mapping and transactional boundaries outside of your Repository code: these things are best left to code which can operate across many DB tables. Often you need to take the result of two separate SQL queries and map the result to a single domain entity. Or sometimes, you want to roll back a transaction (or not execute subsequent SQL) if an initial query fails. I find it best to keep the Repos/DAOs working on a single table/view/sproc (DB entity) to abstract away the details of the Db engine and have domain-layer classes handle the heavy lifting of how to make sense of the data. If you need complex SQL queries with many JOINs, consider creating a view so you can work with the data like any other table.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top