sergezhigunov / hangfire.entityframeworkcore Goto Github PK
View Code? Open in Web Editor NEWAn Entity Framework Core provider-neutral job storage implementation for Hangfire (https://www.hangfire.io).
License: MIT License
An Entity Framework Core provider-neutral job storage implementation for Hangfire (https://www.hangfire.io).
License: MIT License
it seems that there are foreign keys between HangFireState table and HangFireJob table
One of them is set to cascading delete and the other is not
From SQL its relatively easy to adjust but perhaps you handle this in the migration scripts and in the entity model setup ?
SQL used to fix this
ALTER TABLE HangfireJob
DROP FOREIGN KEY FK_HangfireJob_HangfireState_StateId;
ALTER TABLE HangfireJob
ADD CONSTRAINT FK_HangfireJob_HangfireState_StateId FOREIGN KEY (StateId)
REFERENCES HangfireState(Id) ON DELETE CASCADE;
Traces
dbug: Hangfire.EntityFrameworkCore.ExpirationManager[0]
Removing outdated records from the 'HangfireCounter' table...
dbug: Hangfire.EntityFrameworkCore.ExpirationManager[0]
Removing outdated records from the 'HangfireHash' table...
dbug: Hangfire.EntityFrameworkCore.ExpirationManager[0]
Removing outdated records from the 'HangfireList' table...
dbug: Hangfire.EntityFrameworkCore.ExpirationManager[0]
Removing outdated records from the 'HangfireSet' table...
dbug: Hangfire.EntityFrameworkCore.ExpirationManager[0]
Removing outdated records from the 'HangfireJob' table...
dbug: Hangfire.Processing.BackgroundExecution[0]
Execution loop ExpirationManager:043fc230 caught an exception and will be retried in 00:03:45
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Cannot delete or update a parent row: a foreign key constraint fails (PLHFPORTAL
.HangfireJob
, CONSTRAINT FK_HangfireJob_HangfireState_StateId
FOREIGN KEY (StateId
) REFERENCES HangfireState
(Id
))
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable1 commandBatches, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList
1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Hangfire.EntityFrameworkCore.ExpirationManager.<>c__61.<RemoveExpired>b__6_1(DbContext context) at Hangfire.EntityFrameworkCore.EFCoreStorage.UseContext[T](Func
2 func)
at Hangfire.EntityFrameworkCore.ExpirationManager.b__6_0T
at Hangfire.EntityFrameworkCore.ExpirationManager.UseLock(Action action)
at Hangfire.EntityFrameworkCore.ExpirationManager.RemoveExpiredT
at Hangfire.EntityFrameworkCore.ExpirationManager.Execute(CancellationToken cancellationToken)
at Hangfire.Processing.BackgroundExecution.Run(Action`2 callback, Object state)
dbug: Hangfire.Processing.BackgroundExecution[0]
Execution loop ExpirationManager:043fc230 will be retried in 00:03:45...
When we supply the DbContext from the factory, the library uses the dbcontext in a using
statement. This causes the it to be disposed once the task is performed.
This causes a problem when using dependency injection, because if we resolve a service from the service provider, we shouldn't dispose / control its lifetime ourselves, because we're not instantiating it ourselves. So when the DI container tries to share the instance (per lifetime rules i.e. scoped/transient/singleton), it cannot reuse the dbcontext instance again, because the library has disposed it in a previous operation.
services.AddHangfire((provider, configuration) =>
{
var serviceScope = provider.CreateScope(); // share this scope (and dbcontext) among all calls to `contextBuilder` factory.
configuration.UseEFCoreStorage(() => serviceScope.ServiceProvider.GetRequiredService<BlitzDbContext>(),
new EFCoreStorageOptions());
}
);
This is caused by these usages:
and any other places I'm not aware of.
The solution is to remove all using
statements around dbcontext, and let the consumer decide when to dispose it. This library should simply accept whatever the contextBuilder
delegate gives and use it.
The current documented solution is to use a IDbContextFactory<T>
, but it creates a dbcontext for every call to CreateDbContext
, which runs the initialization routine repeatedly.
This brings up the issue that it's causing unnecessary overhead that it shouldn't, though I admit that I haven't benchmarked its effect.
Hi,
I detect an error when testing library. If jobs are expired, this fails because HangfireJobParameter and HangFireState has entries with JobId related.
Error:
`23503: update or delete on table "HangfireJob" violates foreign key constraint "FK_HangfireJobParameter_HangfireJob_JobId" on table "HangfireJobParameter"
DETAIL: Detail redacted as it may contain sensitive data. Specify 'Include Error Detail' in the connection string to include this information.`
A possible solution is to put this on line 73 of the ExpirationManager.cs file:
// Trying remove HangfireJobParameter from expired jobs
context.Set<HangfireJobParameter>().RemoveRange(
context.Set<HangfireJobParameter>().Where(x => expiredEntityIds.Contains(x.JobId)));
// Trying remove HangfireState from expired jobs
context.Set<HangfireState>().RemoveRange(
context.Set<HangfireState>().Where(x => expiredEntityIds.Contains(x.JobId)));
Thanks
I have been using this library together with Hangfire.Console and have come across a limitation with the value size in the HangfireSet table. The console library will store values in HangfireSet and the current schema that is defined has a max size of 100. This makes it frustrating since it means that the max size of a of a log message is 100 characters long, not very large at all!
I propose that the MaxLength on HangfireSet is either removed or at least increased to 256 to match the value that is set in the SQL Server storage.
Happy to make a PR for this you agree with the changes.
Currently the model classes in the project are marked as internal which is normally fine since interaction with the underlying storage should be done with the Hangfire APIs themselves.
However with the addition of EFCore compiled models the generated code that is created from running dotnet ef dbcontext optimize
will have compile errors since it is trying to reference the internal code.
For example, this comes from a sample optimised model
var id = runtimeEntityType.AddProperty(
"Id",
typeof(long),
propertyInfo: typeof(HangfireJob).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(HangfireJob).GetField("<Id>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
valueGenerated: ValueGenerated.OnAdd,
afterSaveBehavior: PropertySaveBehavior.Throw);
The typeof(HangfireJob)
in this case is a compile error since the class is not public.
I propose changing these classes to be public so optimized models will now work. Though it is not really a recommended pattern this will also allow for the use case asked for in #22 to work as well through the dbContext.Set<HangfireJob>()
API.
Happy to make this change and send a PR if you are happy with the concept.
There are a few log records like:
{
"EventId": 0,
"LogLevel": "Debug",
"Category": "Hangfire.Processing.BackgroundExecution",
"Message": "Execution ExpirationManager is in the Faulted state now due to an exception, execution will be retried no more than in 00:00:01",
"Exception": "Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagation(Int32 commandIndex, RelationalDataReader reader) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(RelationalDataReader reader) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable\u00601 commandBatches, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList\u00601 entries) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList\u00601 entriesToSave) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(StateManager stateManager, Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.\u003C\u003Ec.\u003CSaveChanges\u003Eb__104_0(DbContext _, ValueTuple\u00602 t) at Microsoft.EntityFrameworkCore.Storage.NonRetryingExecutionStrategy.Execute[TState,TResult](TState state, Func\u00603 operation, Func\u00603 verifySucceeded) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.DbContext.SaveChanges() at Hangfire.EntityFrameworkCore.ExpirationManager.\u003C\u003Ec__6\u00601.\u003CRemoveExpired\u003Eb__6_1(DbContext context) in D:\\git\\Hangfire.EntityFrameworkCore\\src\\Hangfire.EntityFrameworkCore\\ExpirationManager.cs:line 53 at Hangfire.EntityFrameworkCore.EFCoreStorage.UseContext[T](Func\u00602 func) in D:\\git\\Hangfire.EntityFrameworkCore\\src\\Hangfire.EntityFrameworkCore\\EFCoreStorage.cs:line 186 at Hangfire.EntityFrameworkCore.ExpirationManager.\u003CRemoveExpired\u003Eb__6_0[T]() in D:\\git\\Hangfire.EntityFrameworkCore\\src\\Hangfire.EntityFrameworkCore\\ExpirationManager.cs:line 46 at Hangfire.EntityFrameworkCore.ExpirationManager.UseLock(Action action) in D:\\git\\Hangfire.EntityFrameworkCore\\src\\Hangfire.EntityFrameworkCore\\ExpirationManager.cs:line 67 at Hangfire.EntityFrameworkCore.ExpirationManager.RemoveExpired[T]() in D:\\git\\Hangfire.EntityFrameworkCore\\src\\Hangfire.EntityFrameworkCore\\ExpirationManager.cs:line 44 at Hangfire.EntityFrameworkCore.ExpirationManager.Execute(CancellationToken cancellationToken) in D:\\git\\Hangfire.EntityFrameworkCore\\src\\Hangfire.EntityFrameworkCore\\ExpirationManager.cs:line 30 at Hangfire.Server.ServerProcessDispatcherBuilder.ExecuteComponent(Guid executionId, Object state) at Hangfire.Processing.BackgroundExecution.Run(Action\u00602 callback, Object state)",
"State": {
"Message": "Execution ExpirationManager is in the Faulted state now due to an exception, execution will be retried no more than in 00:00:01"
}
}
Create a unit test project using EFCore 3.0
Implement queue item fetching by one request
Hi
I'm using this library as an adapter between hangfire and my mysql database. Im encountering a runtime exception when the context is building/defining the models. (I'm using EFCore 3.1 as dependency withing my console projects)
This is probably exactly related to dotnet/aspnetcore#8467.
I downloaded the project and updated the package references to EFCore 3.1. Using that project as dependency makes the runtime exception not occur anymore.
I would have done a PR, but i assume updating the EFCore dependency of Hangfire.EntityFrameworkCore from 2.0 to 3.1 could be a backwards incompatible change for all users who are currently depending on the EFCore 2.X releases. Can this be tested? Maybe integrate two test projects where one depends on EFCore 2.X and the other depends on EFCore 3.X directly.
Currently i'm fine using my local copy of this library. I just wanted to let you know of this issue.
Bert
EDIT; Clarification of example projects
if I do this:
services.AddHangfire((serviceProvider, hangfireConfig) =>
hangfireConfig.UseEFCoreStorage(
serviceProvider.GetRequiredService<MyDbContext>,
new EFCoreStorageOptions
{
Schema = "hangfire",
}));
with this in my onmodelcreating
modelBuilder.OnHangfireModelCreating();
i am not seeing the hangfire
schema in my migrations that get generated:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "HangfireCounter",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Value = table.Column<long>(type: "bigint", nullable: false),
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireCounter", x => x.Id);
});
migrationBuilder.CreateTable(
name: "HangfireHash",
columns: table => new
{
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Field = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Value = table.Column<string>(type: "text", nullable: true),
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireHash", x => new { x.Key, x.Field });
});
migrationBuilder.CreateTable(
name: "HangfireList",
columns: table => new
{
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Position = table.Column<int>(type: "integer", nullable: false),
Value = table.Column<string>(type: "text", nullable: true),
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireList", x => new { x.Key, x.Position });
});
migrationBuilder.CreateTable(
name: "HangfireLock",
columns: table => new
{
Id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
AcquiredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireLock", x => x.Id);
});
migrationBuilder.CreateTable(
name: "HangfireServer",
columns: table => new
{
Id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
StartedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Heartbeat = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
WorkerCount = table.Column<int>(type: "integer", nullable: false),
Queues = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireServer", x => x.Id);
});
migrationBuilder.CreateTable(
name: "HangfireSet",
columns: table => new
{
Key = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
Value = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Score = table.Column<double>(type: "double precision", nullable: false),
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireSet", x => new { x.Key, x.Value });
});
migrationBuilder.CreateTable(
name: "HangfireJob",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
StateId = table.Column<long>(type: "bigint", nullable: true),
StateName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
InvocationData = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireJob", x => x.Id);
});
migrationBuilder.CreateTable(
name: "HangfireJobParameter",
columns: table => new
{
JobId = table.Column<long>(type: "bigint", nullable: false),
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Value = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireJobParameter", x => new { x.JobId, x.Name });
table.ForeignKey(
name: "FK_HangfireJobParameter_HangfireJob_JobId",
column: x => x.JobId,
principalTable: "HangfireJob",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "HangfireQueuedJob",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
JobId = table.Column<long>(type: "bigint", nullable: false),
Queue = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
FetchedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireQueuedJob", x => x.Id);
table.ForeignKey(
name: "FK_HangfireQueuedJob_HangfireJob_JobId",
column: x => x.JobId,
principalTable: "HangfireJob",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "HangfireState",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
JobId = table.Column<long>(type: "bigint", nullable: false),
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Reason = table.Column<string>(type: "text", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Data = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_HangfireState", x => x.Id);
table.ForeignKey(
name: "FK_HangfireState_HangfireJob_JobId",
column: x => x.JobId,
principalTable: "HangfireJob",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_HangfireCounter_ExpireAt",
table: "HangfireCounter",
column: "ExpireAt");
migrationBuilder.CreateIndex(
name: "IX_HangfireCounter_Key_Value",
table: "HangfireCounter",
columns: new[] { "Key", "Value" });
migrationBuilder.CreateIndex(
name: "IX_HangfireHash_ExpireAt",
table: "HangfireHash",
column: "ExpireAt");
migrationBuilder.CreateIndex(
name: "IX_HangfireJob_ExpireAt",
table: "HangfireJob",
column: "ExpireAt");
migrationBuilder.CreateIndex(
name: "IX_HangfireJob_StateId",
table: "HangfireJob",
column: "StateId");
migrationBuilder.CreateIndex(
name: "IX_HangfireJob_StateName",
table: "HangfireJob",
column: "StateName");
migrationBuilder.CreateIndex(
name: "IX_HangfireList_ExpireAt",
table: "HangfireList",
column: "ExpireAt");
migrationBuilder.CreateIndex(
name: "IX_HangfireQueuedJob_JobId",
table: "HangfireQueuedJob",
column: "JobId");
migrationBuilder.CreateIndex(
name: "IX_HangfireQueuedJob_Queue_FetchedAt",
table: "HangfireQueuedJob",
columns: new[] { "Queue", "FetchedAt" });
migrationBuilder.CreateIndex(
name: "IX_HangfireServer_Heartbeat",
table: "HangfireServer",
column: "Heartbeat");
migrationBuilder.CreateIndex(
name: "IX_HangfireSet_ExpireAt",
table: "HangfireSet",
column: "ExpireAt");
migrationBuilder.CreateIndex(
name: "IX_HangfireSet_Key_Score",
table: "HangfireSet",
columns: new[] { "Key", "Score" });
migrationBuilder.CreateIndex(
name: "IX_HangfireState_JobId",
table: "HangfireState",
column: "JobId");
migrationBuilder.AddForeignKey(
name: "FK_HangfireJob_HangfireState_StateId",
table: "HangfireJob",
column: "StateId",
principalTable: "HangfireState",
principalColumn: "Id");
}
Using DbContext pooling possibly can improve performance.
Hi,
My code is using the PostgreSQL EnableRetryOnFailure with the DbContext that is passed to the Hangfire.io library. Unfortunately, the ExpirationManager uses BeginTransaction before the SaveChanges is causing the following exception:
System.InvalidOperationException: The configured execution strategy 'NpgsqlRetryingExecutionStrategy' does not support user-initiated transactions. Use the execution strategy returned by 'DbContext.Database.CreateExecutionStrategy()' to execute all the operations in the transaction as a retriable unit. at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.OnFirstExecution() at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.Execute[TState,TResult](TState state, Func
3 operation, Func3 verifySucceeded) at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess) at Hangfire.EntityFrameworkCore.ExpirationManager.<>c.<RemoveExpiredJobs>b__6_1(DbContext context) at Hangfire.EntityFrameworkCore.EFCoreStorage.UseContext[T](Func
2 func)
at Hangfire.EntityFrameworkCore.ExpirationManager.b__6_0()
at Hangfire.EntityFrameworkCore.ExpirationManager.UseLock(Action action)
at Hangfire.EntityFrameworkCore.ExpirationManager.RemoveExpiredJobs()
at Hangfire.EntityFrameworkCore.ExpirationManager.Execute(CancellationToken cancellationToken)
at Hangfire.Processing.BackgroundExecution.Run(Action2 callback, Object state) in C:\projects\hangfire-525\src\Hangfire.Core\Processing\BackgroundExecution.cs:line 118
Should this be able to be a flag to enable / disable transactions, or check to see if the DbContext will support a transaction?
This looks interesting and a bit active. With the churn on dotnet 5 and be using a non-official hangfire storage, an EFCore solution would be better.
Does this work well? What issues do you have? I don't mind helping out on some outstanding work if there's any.
Can you add more documentation on how this library is supposed to work? At first I expected it to expose the DbContext so that I can do the following:
Jobs = await _context.Jobs
// Do more fluent stuff
.ToListAsync(cancellationToken)
But I could not find any way to achieve that. I am interested in having access to the context, so that I can use IQueryable. I also tried my own DbContext, but was getting SqlException: Invalid object name 'HangfireCounter'.
I am trying to check JOB status by in Hangfire its only possible via ID. I would like to use Arguments column e.g. whit WHERE Arguments LIKE.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.