2

How can I allow SignalR to push updates from a SQL Server database to the browser using Entity Framework 6?

Here's my action method:

public ActionResult Index()
{
    var currentGates = _ctx.Transactions
        .GroupBy(item => item.SubGateId)
        .SelectMany(group => group.OrderByDescending(x => x.TransactionDateTime)
        .Take(1))
        .Include(g => g.Card)
        .Include(g => g.Student)
        .Include(g => g.Student.Faculty)
        .Include(g => g.Student.Department)
        .Include(g => g.SubGate)
        .ToList();

    return View(currentGates);
}

After a lot of searching, the only result I got is this:

ASP.NET MVC 5 SignalR, SqlDependency and EntityFramework 6

I have tried the suggested way but it didn't work. In addition to that, I found a very important security issue concerning storing sensitive data in a hidden field!

My question is: How can I update my view according to any Insert on Transaction table?

CarenRose
  • 1,266
  • 1
  • 12
  • 24
Anyname Donotcare
  • 11,113
  • 66
  • 219
  • 392
  • Why didn't it work? – William Xifaras Oct 27 '19 at 22:34
  • No, I have downloaded the app, run it, and it doesn’t update the view + the problem of store sensitive data in a hidden field is so bad. – Anyname Donotcare Oct 27 '19 at 22:37
  • So what exactly is your question? – William Xifaras Oct 27 '19 at 22:37
  • @WilliamXifaras : My question is How to push updates from db to client using EF6 – Anyname Donotcare Oct 28 '19 at 15:10
  • So you are asking how to save data with Entity Framework 6? – William Xifaras Oct 28 '19 at 15:24
  • @WilliamXifaras No I'm asking about how to `Select` the most recent data from db, so if any insertion performed to `transaction` table I can get it in real time – Anyname Donotcare Oct 29 '19 at 09:04
  • Are you aware that SQL Server is a multi-client database? Meaning another user/software might change your data - not just your user/software. – sa.he Oct 31 '19 at 12:48
  • @sa.he: yeah i know, the case is the only source of effect on `transaction` table is when a student use his card on the gate so a record is inserted in the table and i want the operator to see this change immediately on the screen without refresh – Anyname Donotcare Oct 31 '19 at 12:53
  • Is it ONE piece of software that inserts the new records AND needs to push the SignalR notifications? Or are we talking about different ones? – sa.he Oct 31 '19 at 13:06
  • @sa.he When the student check-in, a record is inserted in the `transcation` table in my db so yes, the source to this table is one and only one – Anyname Donotcare Oct 31 '19 at 13:37
  • Sorry, that I keep asking persistently. Sure - you have just one SQLServer. It is a big difference in how to solve your problem if you have an ASP.Net server that is directly notified by all of your gates and then creates the database entries (If this is the scenario @Vince answer is a good way to start). Or do your gates autonomously create database records without the ASP.Net server being involved or directly notified. – sa.he Oct 31 '19 at 13:48
  • can you describe, from where transaction table entry is made ? – Vishal modi Nov 01 '19 at 08:23
  • @sa.he Well,I have six devices with reader which can read the student card and all of them through some manager push those records to one SQL server db, this db I will put triggers on the transaction table so that any new transaction can be inserted into my DB, the problem is: I want any new transaction in my table can be shown immediately to the client with out refresh his screen. – Anyname Donotcare Nov 01 '19 at 15:32
  • @Vishalmodi I describe the process in the last comment, Could U take a look please – Anyname Donotcare Nov 01 '19 at 15:33
  • Is this manager that pushes data into the sql server your code, or is it a 3rd application that you cannot change? – sa.he Nov 05 '19 at 07:29
  • @AnynameDonotcare I see the bounty expired, did my solution not work for you? And if do can you please explain why? – Vince Nov 05 '19 at 11:28
  • @sa.he We will use `superma xpass`https://www.supremainc.com/en/hardware/compact-outdoor-rfid-device-xpass.asp – Anyname Donotcare Nov 05 '19 at 18:53

3 Answers3

5

So basically what you need to do is overwrite the SaveChanges() method and action you SignalR function:

public class ApplicationDbContext : IdentityDbContext<IdentityUser>
  {
  public override int SaveChanges()
        {
            var entities = ChangeTracker.Entries().Where(x => x.Entity is Transactions && x.State == EntityState.Added) ;
            IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
            foreach (var entity in entities)
            {
              hubContext.Clients.All.notifyClients(entity);
            }

            return base.SaveChanges();
         }
    }
Vince
  • 945
  • 7
  • 17
  • This way consider that I have this control over the (Insertion Process), the problem is: I don't have this control over the sdk that pushes the transactions of students to db, so I hope that there's some way to work directly over the db table. – Anyname Donotcare Nov 05 '19 at 18:59
  • @AnynameDonotcare please add this requirement to the original question as its unclear. I usually do things the way vince does but since you don't have control of the process see my answer – johnny 5 Nov 05 '19 at 19:27
3

Normally I would plug into either the service creating the Transaction or the Save changes event like @Vince Suggested However because of your new requirement

the problem is: I don't have this control over the sdk that pushes the transactions of students to db, so I hope that there's some way to work directly over the db table.

In you're case you can just watch the table using SQL Dependencies

Note: Be careful using SqlDependency class - it has problems with memory leaks.

using(var tableDependency = new SqlTableDependency<Transaction>(conString))
{
    tableDependency.OnChanged += TableDependency_Changed;
    tableDependency.Start();
}

void TableDependency_Changed(object sender, RecordChangedEventArgs<Transaction> e)
{
    if (e.ChangeType != ChangeType.None)
    {
        var changedEntity = e.Entity;
        //You'll need to change this logic to send only to the people you want to 
        IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
        hubContext.Clients.All.notifyClients(entity);
    }
}

Edit It seems you may have other dependencies e.g include you want with your results. So what you can do is resolve the Id from the entity and then call Get using EntityFramework.

if (e.ChangeType != ChangeType.None)
{
    var changedEntity = e.Entity;
    var id = GetPrimaryKey(changedEntity)
    _ctx.Transactions.Find(id);
}

Edit Other Methods of approach.

  1. If you're entities have a last updated field you can scan for changes to the table on a timer. Client has a timer when it elapses it send the in the last time it checked for changes. The server the obtains all of entities who have an last update time stamp greater than the time passed in

  2. Although you don't have access to the EF, you probably have access to the web api requests. Whenever someone calls an update or create method, just grab the id of the changed entity, pop all the ids of the entities changed onto a background service which will send the signalr notifications,

The only issue now is obtaining the primary key that can be done with a manual mapping of the types to their id property, or it can be done using the metadata from the model but that's a bit more complicated

Note you probably can modify this so it works generically on all tables

johnny 5
  • 19,893
  • 50
  • 121
  • 195
  • Could I ask where we write this piece of code? (Note: i use EF6 to get the data from the transaction table) and i read there’s no straight way to use sql dependency with. – Anyname Donotcare Nov 05 '19 at 19:30
  • I would create a [Background Service](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.0&tabs=visual-studio) which is in change of listening to changes. That way your HTTP Request are Not affected by the time it takes to send to notifiy your clients. – johnny 5 Nov 05 '19 at 19:33
  • As for the EF6 Usage, Entityframework isn't really needed since, You're not really using the DB for anything other than listening to changes, you may need to create some adapters so the Transaction parses to the EF Class, but that can probably be written generically or done with AutoMapper – johnny 5 Nov 05 '19 at 19:34
  • @AnynameDonotcare you might be able to integrate with EF6 easily see this [article](https://code.msdn.microsoft.com/How-to-use-SqlDependency-5c0da0b3) – johnny 5 Nov 05 '19 at 19:36
  • Thanks alot,I will try the background service, but for your last comment, I already use EF6, Could U look how i compose my data using EF in the action method please – Anyname Donotcare Nov 05 '19 at 19:37
  • @AnynameDonotcare Ah I see, it's because your data, is requires include holdon I'll clarify a way – johnny 5 Nov 05 '19 at 19:39
  • Yeah and i use the returned materialized data (meaningful object) as a model in my view so it’s so important to use EF6 here – Anyname Donotcare Nov 05 '19 at 19:42
  • @AnynameDonotcare I've written up a way to do so, basically, just obtain the id from the changed entity and then call your ef to load the data you need, give me a second and I'll upload a link to help resolve the Id via primarykey – johnny 5 Nov 05 '19 at 19:44
  • @AnynameDonotcare, you can use the id to look up the entity, I have a [method](https://stackoverflow.com/questions/51054890/store-static-filter-by-key-expression) similar which you can modify to help resolve the Id from the primary key, but in your case it might be easier and better to just have a map of all the entities to the Property containing the Id (which 99% of the time im assuming will be called Id) – johnny 5 Nov 05 '19 at 19:47
  • I will try it, Could U help me if I face a problem please – Anyname Donotcare Nov 06 '19 at 17:55
  • Yeah, just ping me on here if you have any issues, it might take a little to respond if im at work – johnny 5 Nov 06 '19 at 18:04
  • @AnynameDonotcare I've alost provided a few more methodologies for solving your issue, I'm not sure of how you have everything set up but 1 of the 3 different routes should work – johnny 5 Nov 06 '19 at 20:51
2

I understand you have the following dataflow:

  • 6x card reader devices
  • a 'manager' receives the card reader data and pushes it to
  • a SQL Server 'transaction' table

All of this is 3rd party. Your goal is to have a custom ASP.NET web page that displays the most recent record received by a card reader.

So the issue basically is, that your ASP.NET service needs to be notified of changes in a monitored database table.

Quick-and-dirty approach:

  1. Implement a database trigger on your big 'transaction' table that updates a lightweight 'state' table

  2. CREATE TABLE state ( 
        GateId   INT          NOT NULL, 
        UserName VARCHAR (20) NOT NULL, 
        PRIMARY KEY (GateId)
    );
    

    This table is intended to have 1 record per gate only. 6 gates -> 6 records in this table.

  3. Change your ASP.NET service to poll the 'state' table in an interval of e.g. 200ms. When doing such a hack, make sure the table you are polling does not have many records! Once you detect a change, notify your SignalR clients.

IMHO, a dataflow via a database is a bad design decision (or limitation). It would be way better if your 'manager' does not only push data to the database, but subsequently notifies all SignalR clients. This dataflow is basically what @Vince answer assumes.

Edit: As you have posted the actual device you are using, I'd encourage you to double-check if you can directly connect to the card reader. It seams that there are approaches to register some kind of callback once the device has read a student card. Do this with the sole goal of achieving a straight dataflow / architecture like this:

-> connect 6x card readers to your 
-> ASP.Net service which at a single point in your code:
   -> updates the database
   -> updates the Signal R clients
CarenRose
  • 1,266
  • 1
  • 12
  • 24
sa.he
  • 1,391
  • 12
  • 25
  • Could U explain more about `Once you detect a change, notify your SignalR clients.` please – Anyname Donotcare Nov 05 '19 at 18:57
  • I suggest to have a look at the Microsoft tutorial of a Real-time chat. https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-getting-started-with-signalr – sa.he Nov 07 '19 at 09:01