ProjectTo<T> i przekazywanie T jako Type

0

Czołem

Mam taką kwestię do rozwiązania...

Chcę skorzystać z generycznej metody i rozszerzenia do auto mapera ProjectTo.
Mianowicie chciałbym zrobić coś takiego, że type w ostrych nawiasach ustalam sobie wcześniej i przypisuję np w klasie TypesOfOrder i później w zależności od warunków przekazuję to jako np Type, a nie nazwa konkretnej klasy do metody generycznej, która wywołuje ProjectTo.

Poniższy kod oczywiście się nie skompiluje, ale przedstawia idee, którą chciałbym zaimplementować.
Możecie wskazać jak prawidłowo rozwiązać taki problem ?

public class TypesOfOrder
{
	public Type OrderA { get; set; }
	
	public Type OrderB { get; set; }
	
	public Type OrderC { get; set; }
}


public async Task<TType> GetDetailsBasedOnOrderAsync<TType>(Expression<Func<Order, bool>> expression, object projectionParameters = null) where TType : Type
{
	var queryable = _chancellorDbContext.Order
									.AsNoTracking()
									.Where(expression)
									.AsSingleQuery();

	return projectionParameters is null
					? await queryable
								.ProjectTo<TType>(_mapper.ConfigurationProvider)
								.SingleOrDefaultAsync()
					: await queryable
								.ProjectTo<TType>(_mapper.ConfigurationProvider, projectionParameters)
								.SingleOrDefaultAsync();
}  

public async Task<object> GetOrderAsync(Guid orderId, Guid orderTypeId, bool forModuleA, bool forModuleB, bool forPdf)
{
	var types = new TypesOfOrder();
	if (forModuleA)
		types = new TypesOfOrder()
				{
					OrderA = typeof(OrderAModuleADto),
					OrderB = typeof(OrderBModuleADto),
					OrderC = typeof(OrderCModuleADto)
				}		
	
	if (forModuleB)
		types = new TypesOfOrder()
				{
					OrderA = typeof(OrderAModuleBDto),
					OrderB = typeof(OrderBModuleBDto),
					OrderC = typeof(OrderCModuleBDto)
				}	
			
	if (forPdf)
		types = new TypesOfOrder()
				{
					OrderA = typeof(OrderAPdfDto),
					OrderB = typeof(OrderBPdfDto),
					OrderC = typeof(OrderCPdfDto)
				}

	if (orderTypeId == OrderAGuid)
		return GetDetailsBasedOnOrderAsync<types.OrderA>(expression);
	
	if (orderTypeId == OrderBGuid)
		return GetDetailsBasedOnOrderAsync<types.OrderB>(expression);
	
	if (orderTypeId == OrderCGuid)
		return GetDetailsBasedOnOrderAsync<types.OrderC>(expression);
	
	return null;
}
1

Ale po co Ci to zapisywać do zmiennej skoro od razu możeszy wywołać return GetDetailsBasedOnOrderAsync<TwójTyp> w zależności od warunku? Jakiś pattern matching np

0
szydlak napisał(a):

Ale po co Ci to zapisywać do zmiennej skoro Od razu możeszy wywołać return GetDetailsBasedOnOrderAsync<TwójTyp> w zależności od warunku? Jakiś pattern matching np

Wtedy bym musiał robić dużo ifów z wywołaniem metody dla konkretnego typu...

if (forModuleA)
{
 if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<TwójTyp>

  if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<InnyTyp>
}

if (forModuleB)
{
 if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<TwójTyp>

  if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<InnyTyp>
}

itd

A tak na końcu bym tylko raz walnął wywołanie metody dla określonego typu.

1
altek napisał(a):
szydlak napisał(a):

Ale po co Ci to zapisywać do zmiennej skoro Od razu możeszy wywołać return GetDetailsBasedOnOrderAsync<TwójTyp> w zależności od warunku? Jakiś pattern matching np

Wtedy bym musiał robić dużo ifów z wywołaniem metody dla konkretnego typu...

if (forModuleA)
{
 if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<TwójTyp>

  if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<InnyTyp>
}

if (forModuleB)
{
 if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<TwójTyp>

  if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<InnyTyp>
}

itd

A tak na końcu bym tylko raz walnął wywołanie metody dla określonego typu.

Nie musiałbyś:
https://endjin.com/blog/2022/02/pattern-matching-in-csharp

0
szydlak napisał(a):
altek napisał(a):
szydlak napisał(a):

Ale po co Ci to zapisywać do zmiennej skoro Od razu możeszy wywołać return GetDetailsBasedOnOrderAsync<TwójTyp> w zależności od warunku? Jakiś pattern matching np

Wtedy bym musiał robić dużo ifów z wywołaniem metody dla konkretnego typu...

if (forModuleA)
{
 if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<TwójTyp>

  if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<InnyTyp>
}

if (forModuleB)
{
 if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<TwójTyp>

  if (orderTypeId == cośtam)
  GetDetailsBasedOnOrderAsync<InnyTyp>
}

itd

A tak na końcu bym tylko raz walnął wywołanie metody dla określonego typu.

Nie musiałbyś:
https://endjin.com/blog/2022/02/pattern-matching-in-csharp

Przeczytałem, ale jakoś średnio wiem jak mogę to zastosować do swojego przypadku. Możesz nakreślić jakiś zarys ?

2

Coś takiego można by zastosować

return (forModuleA,forModuleA,forPdf, orderTypeId) switch 
{
	(_,_,true, OrderAGuid) => GetDetailsBasedOnOrderAsync<OrderAPdfDto>(expression);
	(_,_,true, OrderBGuid) => GetDetailsBasedOnOrderAsync<OrderBPdfDto>(expression);
	(_,_,true, OrderCGuid) => GetDetailsBasedOnOrderAsync<OrderCPdfDto>(expression);
	
	(_,true,false, OrderAGuid) => GetDetailsBasedOnOrderAsync<OrderAModuleBDto>(expression);
	(_,true,false, OrderBGuid) => GetDetailsBasedOnOrderAsync<OrderBModuleBDto>(expression);
	(_,true,false, OrderCGuid) => GetDetailsBasedOnOrderAsync<OrderCModuleBDto>(expression);
	
	(true,false,false, OrderAGuid) => GetDetailsBasedOnOrderAsync<OrderAModuleADto>(expression);
	(true,false,false, OrderBGuid) => GetDetailsBasedOnOrderAsync<OrderBModuleADto>(expression);
	(true,false,false, OrderCGuid) => GetDetailsBasedOnOrderAsync<OrderCModuleADto>(expression);
}
1

Teoretycznie da się to zrobić w taki sposób, ale nie szedłbym w coś takiego (raczej wolę unikać skomplikowanej refleksji).


public class Class
    {
        public static Guid OrderAGuid { get; private set; } = Guid.NewGuid();

        public static Guid OrderBGuid { get; private set; } = Guid.NewGuid();

        public static Guid OrderCGuid { get; private set; } = Guid.NewGuid();   

        public class TypesOfOrder
        {
            public Type OrderA { get; set; }

            public Type OrderB { get; set; }

            public Type OrderC { get; set; }
        }

        private Dictionary<Guid, Dictionary<string, Type>> Dictionary = new Dictionary<Guid, Dictionary<string, Type>>()
        {
            { OrderAGuid, new Dictionary<string, Type>()
                {
                    { "ModuleA", typeof(OrderAModuleADto) },
                    { "ModuleB", typeof(OrderBModuleADto) },
                    { "PDF", typeof(OrderBModuleADto) }
                }
            },
            { OrderBGuid, new Dictionary<string, Type>()
                {
                    { "ModuleA", typeof(OrderAModuleADto) },
                    { "ModuleB", typeof(OrderBModuleADto) },
                    { "PDF", typeof(OrderBModuleADto) }
                }
            },
            { OrderCGuid, new Dictionary<string, Type>()
                {
                    { "ModuleA", typeof(OrderAModuleADto) },
                    { "ModuleB", typeof(OrderBModuleADto) },
                    { "PDF", typeof(OrderBModuleADto) }
                }
            }
        };

        public Task<TType> GetDetailsBasedOnOrderAsync<TType>(Expression<Func<Order, bool>> expression, object projectionParameters = null) 
        {
            var result = (TType)Convert.ChangeType(new OrderAModuleADto(), typeof(TType));
            return Task.FromResult(result);
        }

        public async Task<object> GetOrderAsync(Guid orderId, Guid orderTypeId, string moduleName)
        {
            var type = Dictionary[orderTypeId][moduleName];
            var method = GetType()
                .GetMethod("GetDetailsBasedOnOrderAsync")
                ?.MakeGenericMethod(type);

            var result = method.Invoke(this, new object[] { null, null });

            Task task = (Task)result;
            await task.ConfigureAwait(false);

            return (object)((dynamic)task).Result;
        }
    }
3

Zazwyczaj biblioteki które udostępniają generyczne metody, udostępniają też przeciążenie przyjmujące Type jako parametr do wywołania w runtime, tak jest i w tym przypadku:

https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/QueryableExtensions/Extensions.cs#L56

Czyli po prostu zamiast:

queryable.ProjectTo<TType>(_mapper.ConfigurationProvider)

piszesz:

queryable.ProjectTo(typeof(TType), _mapper.ConfigurationProvider)

teraz typ możesz przyjąć jako parametr i tyle.
Oczywiście jako wynik dostajesz IQueryable zamiast IQueryable<TType> więc wynik musisz sobie też rzutować na (TType) ale to nie ma znaczenia bo Twoje GetOrderAsync zwraca tak czy inaczej Task<object>

1 użytkowników online, w tym zalogowanych: 0, gości: 1