All streams
Search
Write a publication
Pull to refresh
4
0
Send message

Интересует скорость выполнения на сотнях миллионах записей, остальное в документации описано да и не далеко от стандарта ушло или даже не убежало.

Согласен с предыдущим постом. Orleans именно то что здесь нужно.

Они хотят заменить .NET Framework на .NET 5.
Значит надо все WinForms, WPF и тд. тянуть на себя. Никто от вас линукс не забирает, но также и никто не будет WinForms портировать под него — просто не ставьте не кросплатформенные библиотеки.

Для балансировки нагрузки и масштабирования я имею опыт использования ApacheMq

Посмотрел, что-то больно на Java мир завязано. И разве это не брокер сообщений?
Я бы его больше к этому проекту сравнивал: https://github.com/dapr/dapr


В общем-то те же фичи что описаны Вами для Microsoft Orleans, но плюс еще кросс-платформенность

.NET Core давно кросплатформенный как бы.

Мне иетересно, пробовали ли вы Microsoft Orleans? Как на меня отличнейшее решение для 90% случаев. Но тут я пока только как теоретик, пару раз кластер подымал — проще ничего нету.
Первое что в голову приходит


  • Строгая типизация реквестов через интерфейсы со скоростной сериализацией
  • Автоматическая балансировка вплоть до миграции воркера на другую машину с состоянием (естественно надо persistanse писать)
  • Автоматический failover
  • Поддержка кластерного singleton по умолчанию. Удобно, знеаете ли, знать что три машины не колбасят ту же задачу или данные.
  • Distributed cache в силу того что работает предыдущий пункт
  • Встроенные события/потоки/таймеры/remainders
  • Discovery

Можно еще покопать, но как на меня профит очевиден: пишите себе на чистом .NET забыв о swagger. Расслабляетесь от балансировки "микросервисов", от толпы не нужных инстансов. Не конфигурируете кеши, очереди (ну без них не всегда можно).


Сумбурно как-то так

items.Count()

Не затянули ли вы всю таблицу чтобы подсчитать количество записей?
Ну в общем то что заметил, разве что сложные отчеты это не к EF :).

Не все упирается в выборку, хотя тут linq2db даст фору по качеству SQL.
Например изменение записи в EF это два обращения к базе данных, и то желательно в транзакции.


  • Достать обьект из базы полностью
  • Изменить свойство
  • SaveChanges

В linq2db это однин стейтмент и ничего на клиент тянуть не пришлось


   db.Products
     .Where(p => p.ProductId == dto.ProductId)
     .Set(p => p.SoldDate, () => Sql.CurrentTimestamp)
     .Set(p => p.IsSold, true)
     .Update();

Конечно возможно приаттачить обьект к ChangeTracker, только делают это редко.
Также из примера видно что мы использовали текущую дату сервера. Что в EF проблематично.


Вот из таких кирпичиков и растет перформанс. Чем дальше мы от SQL, тем больше мы полагаемся на архитектуру всемогутора, а она не всегда эфективна.

Это риторический вопрос?
На всякий случай вот dotnet.github.io/orleans
> Не может этот запрос быть быстрее обычного count без оконной функции

Как бы очевидно.
Но план запроса строится один раз в отличии от двух запросов в одном раундтрипе. И мало какой ADO.NET провайдер такое поддерживает. Сказал же в полтора раза быстрее.
Если все-же, количество записей важно, не забываем об оконных функциях.

SELECT COUNT(*) OVER () as TotalCount, * 
FROM Sales.SalesOrderHeader
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY


Теперь в каждой записи будет зашито реальное количество записей. Достаточно прочитать из первой записи это значение, а дальше игнорировать. На вскидку, получается в полтора раза быстрее и один запрос к базе.

Естественно это будет работать только если вы не применяли другие техники выбора страницы.
Ну, можно присмотреться к этой библиотеке, человеку очень не хватало монад и функционального в C#. Все никак не соберусь попробовать, пугает на сколько может упасть скорость работы и что все-таки компилятор с JIT соптимизируют.
github.com/louthy/language-ext
Да, есть библиотека для бескровного склеивания двух миров. Асинки вот только конфликтуют, для решения этой ситуации добавлены дополнительные экстеншины.
github.com/linq2db/linq2db.EntityFrameworkCore
В названии статьи отсутствует LINQ by EF. Так как оно не отражает полную картину. EF всегда был калекой в трансляции дерева выражений в SQL.

Практически все эти детские болезни мы полечили в linq2db. Клеить строки не надо, Dapper не надо, делать хранимки на каждый чих не надо.

  • Хинты можно использовать
  • CTE из каробки, вместе с рекурсивными CTE
  • Union, Concat собирает все что надо, хоть завались ими
  • INSERT FROM, INSERT INTO, UPDATE FROM...
  • Менять названия таблиц налету
  • Использовать временные таблицы
  • Оконные функции из каробки
  • BulkCopy на все поддерживаемые базы данных
  • CRUD по CQRS паттерну, ничего лишнего вплоть до единственного поля
  • ...


“Но они продолжали жевать кактус и дискутировать какой generic repository лучше и когда стартовать UoW.”
Я на визиторы смотрю как на ужас. Наверное наша ORM давно бы погрязла в куче кассов и непонятных иерархий.
Простая, постоянно требующя решения задача подмены параметра в теле лямбды превращается в армагеддон. Да она решается созданием одного ReplacingExpressionVisitor (такой себе класик)
И такого вызова:

var newParam = Expression.Parameter(someType); 
var newBody = ReplacingExpressionVisitor.Replace(lambda.Paramters[0], newParam, lambda.Body);

А еще она решаться может и вот так:
var newParam = Expression.Parameter(someType); 
var newBody = lambda.Body.Transform(e => e == lambda.Paramters[0] ? newParam : e);


Вроде одинаково, но гипотетически усложним задачу, мало того, что надо подменить параметр, так и прибавить единички ко всем целочисленным константам. Тут уже надо городить новые классы или даже пропускать через несколько визиторов, а мы в лямбде для Transform просто усложняем условие. Это может быть ощутимо заметно на гигантском дереве, такие иногда получаются когда запрос к базе содержит монструидальный Where, созданный динамичиски какой-то UI библиотекой.

Минус Transform в том что если вы придумываете кастомные Expression, тут уже сильно не потрансформируешь, или придется расширять метод.

Если интересна реализация, ее можно найти в библиотеке CodeJam или в нашей ORM linq2db.

В защиту данного подхода скажу так, за 5 лет мне ни разу не пришлось писать новый Visitor.
Вы не понимаете что такое энумераторы и роль yield return в коде парсера.

Я бы советовал подучить эту часть.
Как только, кто-то начинает энумерацию, создается стейт машина (класс в который превращается ParseCsv метод компилятором) и при вызове метода Next в энумераторе из файла читается одна линия, не больше. Когда файл заканчивается Next вернет false — все мы перебрали все линии.
Не учить же вас как пользоваться C#. Вот примерная реализация парсера. Пишу на коленке, важен подход.
public IEnumerable<string[]> ParseCsv(string fileName)
{
   using (var file = new System.IO.StreamReader(fileName))
   {
       string line;
       while((line = file.ReadLine()) != null)  
       {  
            yield return line.Split(‘\t’);
       }
    }
}

linq2db внутри считывает из этого энумератора только 1000 записей и запихивает это в BulkCopy (для MSSQL) или готовит компактный SQL с параметрами на всю 1000 записей. Объекты больше не нужны и их подберет GC.
Процесс повторяется пока до конца файла не дойдем. Энумератор диспозится и файл закрывается.

Точно также можно взять данные IEnumerable из одной базы и закинуть в другую. Не важно сколько записей в исходной таблице, хоть миллионы.
Вся магия внутри, по умолчанию обрабатываются пачки по 1000 записей, и это конфигурируется. Догадываетесь почему?

Вы пропустили мое описание что данные должны представляться как IEnumerable — это не все записи сразу, ни в коем случае.
Могу подкинуть другой вариант:
Берется linq2db
Берется любая библиотека которая CSV парсит и потоково возвращает IEnumerable<string[]>, тут я не помощник.

И пишется что-то такое
var csvItems = CSVParser.Parse("filename");

using (var db = new DataConnection(...))
{
    db.BulkCopy(
       csvItems.Select(csv => new DestinationEntity{
          Field1 = int.Parse(csv[0]),
          Field2 = double.Parse(csv[1]),
          LongStr = csv[2],
          ...
       })
    );
}

Гарантирую, что если отключите индексы, данные влетят, даже чай не успеете заварить.
Для баз данных, которые поддерживают такие вставки, и того быстрее. Будут посланы параметризированные запросы и не надо беспокоиться о длине полей.
1) Возможно, специфика есть специфика
2) Так и делают. Тянут ORM на клиент, хотя в случае EF Core это толпа зависимостей. По этому часто используют Remote LINQ или что-то в этом роде.

О случае когда вам IQueryable не хватило можно поподробнее?

p.s. код лучше было оформить в виде ссылки на codebin или что-то вроде того

Возможно, но так как писано на коленке, тогда и не задумывался.
Я, чесно, пока не понимаю зачем вы так усложняете себе жизнь. За час на коленке я написал простенькое решение, которое умеет комбинировать фильтры. Все просто, вы пишете IQueryable расширения и комбинируете как угодно.
К примеру используя одни и те же методы:

class Program
{
   static void Main(string[] args)
   {
       var query = new[]
       {
           new SomeClass { City = "NY", Years = 20 },
           new SomeClass { City = "Seattle", Years = 16 },
       }.AsQueryable();

        var semi = query.CombineOr(q => q.IsReadyToDrink(), q => q.LiveIn("NY", "Seattle"))
            .ToArray();

        var strict = query
            .IsReadyToDrink()
            .LiveIn("NY", "Seattle")
            .ToArray();


        var semi2 = query.CombineOr(q => q.CityAndDrinkability("NY", "Seattle"), q => q.IsReadyToDrink())
        .ToArray();

        var strict2 = query
            .CityAndDrinkability("NY", "Seattle")
            .ToArray();
   }
}

Здесь помогают интерфейсы, которые вы зададите своим сущностям:

public interface IAge
{
    int Years { get; }
}

public interface ICity
{
    string City { get; }
}

public class SomeClass : IAge, ICity
{
    public string SomeValue { get; set; }
    public int Years { get; set; }
    public string City { get; set; }
}

public static class BusinessRules
{
    public static IQueryable<T> IsReadyToDrink<T>(this IQueryable<T> source)
        where T: IAge
    {
        return source.Where(s => s.Years >= 18).Where(s => s.Years <= 70);
    }

    public static IQueryable<T> LiveIn<T>(this IQueryable<T> source, 
          params string[] cities)
        where T: ICity
    {
        return source.Where(s => cities.Contains(s.City));
    }

    public static IQueryable<T> CityAndDrinkability<T>(this IQueryable<T> source, params string[] cities)
        where T: ICity, IAge
    {
        return source.LiveIn(cities).IsReadyToDrink();
    }
}

Ну и сама реализация (использован метод Transform из билиотеки CodeJam)

public static class SpecsExtensions
{
    private static Expression Unwrap(Expression expr)
    {
        if (expr.NodeType == ExpressionType.Quote)
            return Unwrap(((UnaryExpression) expr).Operand);
        return expr;
    }

    private static IEnumerable<Expression> CollectCondition(Expression query, ParameterExpression obj)
    {
        if (query.NodeType == ExpressionType.Call)
        {
            var mc = (MethodCallExpression) query;
            if (mc.Method.IsGenericMethod && mc.Method.GetGenericMethodDefinition() == _whereMethodInfo)
            {
                var unwrapped = (LambdaExpression)Unwrap(mc.Arguments[1]);
                foreach (var cond in CollectCondition(mc.Arguments[0], obj))
                {
                    yield return cond;
                }

                var corrected = unwrapped.Body.Transform(e => e == unwrapped.Parameters[0] ? obj : e);

                yield return corrected;
            }
            else
            {
                var canProcess = mc.Method.DeclaringType != typeof(Queryable) && mc.Arguments.Count > 0;
                if (canProcess)
                {
                    canProcess = mc.Arguments[0].Type.IsGenericType;
                    if (canProcess)
                    {
                        canProcess = mc.Arguments[0].Type.GetGenericTypeDefinition() == typeof(IQueryable<>);
                    }
                }

                if (!canProcess)
                {
                    throw new NotImplementedException();
                }

                // processing user defined functions, so filters can be reused in other filters
                var innerExpression = ((IQueryable) Expression.Lambda(mc).Compile().DynamicInvoke()).Expression;
                foreach (var cond in CollectCondition(innerExpression, obj))
                {
                    yield return cond;
                }               
            }

        }
    }

    public static MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
    {
        var member = expression.Body as MethodCallExpression;

        if (member != null)
            return member.Method;

        throw new ArgumentException("Expression is not a method", "expression");
    }

    private static readonly MethodInfo _whereMethodInfo = GetMethodInfo<IQueryable<int>>(q => q.Where((Expression<Func<int, bool>>)null)).GetGenericMethodDefinition();

    public static IQueryable<T> CombineOr<T>(this IQueryable<T> source, params Func<IQueryable<T>, IQueryable<T>>[] queries)
    {
        Expression condition = null;
        var param = Expression.Parameter(typeof(T), "q");
        var fake = Enumerable.Empty<T>().AsQueryable();
        foreach (var query in queries)
        {
            var filter = query(fake);
            var strict = CollectCondition(filter.Expression, param)
                .Aggregate(Expression.AndAlso);

            condition = condition == null ? strict : Expression.OrElse(condition, strict);
        }

        if (condition == null)
            return source;

        var filterBody = Expression.Lambda(condition, param);

        var result = (IQueryable<T>) _whereMethodInfo.MakeGenericMethod(typeof(T))
            .Invoke(null, new object[] { source, filterBody });

        return result;
    }
}

Как на меня просто, наглядно и легко понимаемо. Да тут шаманство с деревьями выражений, но я это сделал за вас.
Все проверяется компилятором и никаких дополнительных абстракций.

Information

Rating
Does not participate
Registered
Activity