Они хотят заменить .NET Framework на .NET 5.
Значит надо все WinForms, WPF и тд. тянуть на себя. Никто от вас линукс не забирает, но также и никто не будет WinForms портировать под него — просто не ставьте не кросплатформенные библиотеки.
Для балансировки нагрузки и масштабирования я имею опыт использования ApacheMq
Посмотрел, что-то больно на Java мир завязано. И разве это не брокер сообщений?
Я бы его больше к этому проекту сравнивал: https://github.com/dapr/dapr
В общем-то те же фичи что описаны Вами для Microsoft Orleans, но плюс еще кросс-платформенность
Мне иетересно, пробовали ли вы Microsoft Orleans? Как на меня отличнейшее решение для 90% случаев. Но тут я пока только как теоретик, пару раз кластер подымал — проще ничего нету.
Первое что в голову приходит
Строгая типизация реквестов через интерфейсы со скоростной сериализацией
Автоматическая балансировка вплоть до миграции воркера на другую машину с состоянием (естественно надо persistanse писать)
Автоматический failover
Поддержка кластерного singleton по умолчанию. Удобно, знеаете ли, знать что три машины не колбасят ту же задачу или данные.
Distributed cache в силу того что работает предыдущий пункт
Встроенные события/потоки/таймеры/remainders
Discovery
Можно еще покопать, но как на меня профит очевиден: пишите себе на чистом .NET забыв о swagger. Расслабляетесь от балансировки "микросервисов", от толпы не нужных инстансов. Не конфигурируете кеши, очереди (ну без них не всегда можно).
Не все упирается в выборку, хотя тут linq2db даст фору по качеству SQL.
Например изменение записи в EF это два обращения к базе данных, и то желательно в транзакции.
Достать обьект из базы полностью
Изменить свойство
SaveChanges
В linq2db это однин стейтмент и ничего на клиент тянуть не пришлось
Конечно возможно приаттачить обьект к ChangeTracker, только делают это редко.
Также из примера видно что мы использовали текущую дату сервера. Что в EF проблематично.
Вот из таких кирпичиков и растет перформанс. Чем дальше мы от SQL, тем больше мы полагаемся на архитектуру всемогутора, а она не всегда эфективна.
> Не может этот запрос быть быстрее обычного 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
Я на визиторы смотрю как на ужас. Наверное наша 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 из одной базы и закинуть в другую. Не важно сколько записей в исходной таблице, хоть миллионы.
Могу подкинуть другой вариант:
Берется 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;
}
}
Как на меня просто, наглядно и легко понимаемо. Да тут шаманство с деревьями выражений, но я это сделал за вас.
Все проверяется компилятором и никаких дополнительных абстракций.
Интересует скорость выполнения на сотнях миллионах записей, остальное в документации описано да и не далеко от стандарта ушло или даже не убежало.
Согласен с предыдущим постом. Orleans именно то что здесь нужно.
Они хотят заменить .NET Framework на .NET 5.
Значит надо все WinForms, WPF и тд. тянуть на себя. Никто от вас линукс не забирает, но также и никто не будет WinForms портировать под него — просто не ставьте не кросплатформенные библиотеки.
Посмотрел, что-то больно на Java мир завязано. И разве это не брокер сообщений?
Я бы его больше к этому проекту сравнивал: https://github.com/dapr/dapr
.NET Core давно кросплатформенный как бы.
Мне иетересно, пробовали ли вы Microsoft Orleans? Как на меня отличнейшее решение для 90% случаев. Но тут я пока только как теоретик, пару раз кластер подымал — проще ничего нету.
Первое что в голову приходит
Можно еще покопать, но как на меня профит очевиден: пишите себе на чистом .NET забыв о swagger. Расслабляетесь от балансировки "микросервисов", от толпы не нужных инстансов. Не конфигурируете кеши, очереди (ну без них не всегда можно).
Сумбурно как-то так
Не затянули ли вы всю таблицу чтобы подсчитать количество записей?
Ну в общем то что заметил, разве что сложные отчеты это не к EF :).
Не все упирается в выборку, хотя тут linq2db даст фору по качеству SQL.
Например изменение записи в EF это два обращения к базе данных, и то желательно в транзакции.
В linq2db это однин стейтмент и ничего на клиент тянуть не пришлось
Конечно возможно приаттачить обьект к ChangeTracker, только делают это редко.
Также из примера видно что мы использовали текущую дату сервера. Что в EF проблематично.
Вот из таких кирпичиков и растет перформанс. Чем дальше мы от SQL, тем больше мы полагаемся на архитектуру всемогутора, а она не всегда эфективна.
На всякий случай вот dotnet.github.io/orleans
Как бы очевидно.
Но план запроса строится один раз в отличии от двух запросов в одном раундтрипе. И мало какой ADO.NET провайдер такое поддерживает. Сказал же в полтора раза быстрее.
Теперь в каждой записи будет зашито реальное количество записей. Достаточно прочитать из первой записи это значение, а дальше игнорировать. На вскидку, получается в полтора раза быстрее и один запрос к базе.
Естественно это будет работать только если вы не применяли другие техники выбора страницы.
github.com/louthy/language-ext
github.com/linq2db/linq2db.EntityFrameworkCore
Практически все эти детские болезни мы полечили в linq2db. Клеить строки не надо, Dapper не надо, делать хранимки на каждый чих не надо.
“Но они продолжали жевать кактус и дискутировать какой generic repository лучше и когда стартовать UoW.”
Простая, постоянно требующя решения задача подмены параметра в теле лямбды превращается в армагеддон. Да она решается созданием одного ReplacingExpressionVisitor (такой себе класик)
И такого вызова:
А еще она решаться может и вот так:
Вроде одинаково, но гипотетически усложним задачу, мало того, что надо подменить параметр, так и прибавить единички ко всем целочисленным константам. Тут уже надо городить новые классы или даже пропускать через несколько визиторов, а мы в лямбде для Transform просто усложняем условие. Это может быть ощутимо заметно на гигантском дереве, такие иногда получаются когда запрос к базе содержит монструидальный Where, созданный динамичиски какой-то UI библиотекой.
Минус Transform в том что если вы придумываете кастомные Expression, тут уже сильно не потрансформируешь, или придется расширять метод.
Если интересна реализация, ее можно найти в библиотеке CodeJam или в нашей ORM linq2db.
В защиту данного подхода скажу так, за 5 лет мне ни разу не пришлось писать новый Visitor.
Я бы советовал подучить эту часть.
Как только, кто-то начинает энумерацию, создается стейт машина (класс в который превращается ParseCsv метод компилятором) и при вызове метода Next в энумераторе из файла читается одна линия, не больше. Когда файл заканчивается Next вернет false — все мы перебрали все линии.
linq2db внутри считывает из этого энумератора только 1000 записей и запихивает это в BulkCopy (для MSSQL) или готовит компактный SQL с параметрами на всю 1000 записей. Объекты больше не нужны и их подберет GC.
Процесс повторяется пока до конца файла не дойдем. Энумератор диспозится и файл закрывается.
Точно также можно взять данные IEnumerable из одной базы и закинуть в другую. Не важно сколько записей в исходной таблице, хоть миллионы.
Вы пропустили мое описание что данные должны представляться как IEnumerable — это не все записи сразу, ни в коем случае.
Берется linq2db
Берется любая библиотека которая CSV парсит и потоково возвращает IEnumerable<string[]>, тут я не помощник.
И пишется что-то такое
Гарантирую, что если отключите индексы, данные влетят, даже чай не успеете заварить.
Для баз данных, которые поддерживают такие вставки, и того быстрее. Будут посланы параметризированные запросы и не надо беспокоиться о длине полей.
2) Так и делают. Тянут ORM на клиент, хотя в случае EF Core это толпа зависимостей. По этому часто используют Remote LINQ или что-то в этом роде.
О случае когда вам IQueryable не хватило можно поподробнее?
Возможно, но так как писано на коленке, тогда и не задумывался.
К примеру используя одни и те же методы:
Здесь помогают интерфейсы, которые вы зададите своим сущностям:
Ну и сама реализация (использован метод Transform из билиотеки CodeJam)
Как на меня просто, наглядно и легко понимаемо. Да тут шаманство с деревьями выражений, но я это сделал за вас.
Все проверяется компилятором и никаких дополнительных абстракций.