简体   繁体   中英

How can i select generic columns from a table but make sure my keycolumn is selected too?

I have a table in my database with a lot of columns. I want to have a class where i load columns specified in the constructor into a list. I dont want to load all columns because that takes too long. Additionaly i may want to apply functions on specific columns becuase some data needs to be sanitized. Later i want to be able to return rows from this list by a keycolumn that is fixed (no need to specify it in the constructor). This is kinda what i want:

public class DataHolder<TType> where TType:class
{
    private List<TType> _data;
    public DataHolder(DataContext context,Expression<Func<MyTable, TType>> select)
    {
        _data = context.MyTable.Select(select).DoSanitation().ToList();
        //do sanitation on a column if it is in _data here
    }
   
    
    public TType Get(int id)
    {
        return _data.Single(d => d.Id == id);
    }
}

And then i want to use it kinda like this:

var datHolder = new DataHolder(context, x=> new{x.Column1,x.Column2});
var row= datHolder.Get(123);

And row should have the fields "Column1" and "Column2" and "Id".

So i tried it by using anonymous types but because anonymous types cant use interfaces i am not able to make sure the type has the field "Id". Also the whole sanitation thing doesnt make sense on a anonymous type. I have the sense that i am doing something i should not do or am not seeing a simple solution. I also had a look into Ado.Net which seems like it solve my problems because i can assemble columns adhoc. But all my other code runs with ef core so i am not sure if i should proceed in that direction.

You can't do this with anonymous types, but with types, known at compile time, you can do something like this:


public interface IEntity
{
    public int Id { get; }
}

public class DataHolder<TType>
    where TType : class, IEntity
{
    private static readonly Lazy<IEnumerable<PropertyInfo>> MyTableProperties = new Lazy<IEnumerable<PropertyInfo>>(() => GetPublicInstanceProperties<MyTable>());
    private static readonly Lazy<Expression<Func<MyTable, TType>>> Selector = new Lazy<Expression<Func<MyTable, TType>>>(GetSelector);
    private readonly IReadOnlyDictionary<int, TType> data;

    public DataHolder(MyContext context, Action<TType> doSanitation)
    {
        var entities = context.MyTable
            .Select(Selector.Value)
            .ToList();

        foreach (var entity in entities)
        {
            doSanitation(entity);
        }

        data = entities.ToDictionary(_ => _.Id);
    }

    public TType Get(int id) => data[id];

    private static Expression<Func<MyTable, TType>> GetSelector()
    {
        var lambdaParameter = Expression.Parameter(typeof(MyTable));

        var memberBindings = GetPublicInstanceProperties<TType>()
            .Select(propertyInfo => Expression.Bind(propertyInfo, Expression.MakeMemberAccess(lambdaParameter, MyTableProperties.Value.FirstOrDefault(p => p.Name == propertyInfo.Name))));

        var memberInit = Expression.MemberInit(Expression.New(typeof(TType)), memberBindings);

        return Expression.Lambda<Func<MyTable, TType>>(memberInit, lambdaParameter);
    }

    private static IEnumerable<PropertyInfo> GetPublicInstanceProperties<T>() => typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
}

GetSelector method is just a simple mapper: it assigns property of TType object from the property of MyTable object with the same name.

Usage:

using (var context = new MyContext())
{
    var dataHolder = new DataHolder<EntityA>(context, entity => 
    { 
        // TODO: 
    });

    var row = dataHolder.Get(1);
}

where EntityA is:

public class EntityA : IEntity
{
    public int Id { get; set; }

    public int A { get; set; }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM