0

I am writing a windows service that listens on a port for http messages. These messages are from a RFID tag reader. The listening portion is done by a closed source API that I plan on replacing later if need be with my own code. I have an eventHandler that is fired every time the reader API receives a message. It then added the data to a msSQL database, using entity code first.

The problem is the two routes I have explored so far have major draw backs.

I can create a local instance of my context in the handler method, or I can create a class wide instance. The problem with the local instance is that it has to cache the database (I think) every time it runs which seems to take a considerable amount of time. I've used a stop watch to time the function start to end. With the context created locally it can take up to 3 minutes for the function to run.

The other option is to use a class wide instance but the problem with that is, that the cache isn't updated so that if I change something in the database the method won't see it. I have a bool property in the reader class that that determines whether to send an email notification or not.

I've timed this and it averages about 5 seconds to run.

Here is my abbreviated eventHandler code. With the second route.

private DatabaseAPI databaseAPI; //** inherits from dbcontext
private static readonly object HandleMsgLock = new object();

private void handleMsgRecvEvent(string msg)
{
    lock (HandleMsgLock)
    {

        bool error = false;

        bool failed = false;

        NotifyInfo info = new NotifyInfo();

        AlienUtils.ParseNotification(msg, out info);

        Reader reader = databaseAPI.GetReader(info.MACAddress);

        if (reader != null)
        {
            Reading reading = new Reading();

            if (info.TagList != null)
            {

                //** fix me, this could use some improvements
                for (int i = 0; i < reader.Antennas.Count; i++) //** loop through each antenna
                {
                    reading = new Reading();

                    reading.Antenna = reader.Antennas.Where(x => x.AntennaNumber == i).SingleOrDefault();
                    reading.TargetNumOfTags = reading.Antenna.numOfTag;

                    List<ITagInfo> tagList = info.TagList.Where(x => x.Antenna == i).ToList();
                    if (tagList.Count == reading.Antenna.numOfTags)
                    {
                        log("- - - - - - -" + "\n");
                        log("TagStatus = OK \n");

                        reading.Status = Reading.StatusType.Pass;
                        reading.numOfTagsRead = (short)tagList.Count;
                    }
                    else
                    {
                        failed = true;
                        reading.Status = Reading.StatusType.Fail;
                        reading.numOfTagsRead = (short)tagList.Count;
                    }

                    foreach (ITagInfo t in tagList)
                    {
                        Tag tag = databaseAPI.GetTag(t.TagID);

                        if (tag == null)
                        {
                            tag = databaseAPI.CreateTag(t.TagID, reading);
                        }
                        else
                        {
                            //** if tag number does exist add reading to it
                            tag.Readings.Add(reading);
                        }

                        tag.TagID = t.TagID;
                        reading.Tags.Add(tag);
                    }

                    reading.Reader = reader;
                    reading.TimeStamp = DateTime.Now;
                    databaseAPI.Readings.Add(reading);

                    if (failed)
                    {
                        handleShoeFailed(reading);
                    }
                    else
                    {
                        handleShoePassed(reading);
                    }

                    log("TagsRead: " + tagList.Count + " out of " + reading.Antenna.numOfTags + "\n");

                }//** end for loop

            }//** end taglistCount not null
            else
            {
                reading.Antenna = null;
                reading.Status = Reading.StatusType.None;
                reading.TargetNumOfTags = reader.numOfTags;
                reading.Reader = reader;
                reading.TimeStamp = DateTime.Now;
                databaseAPI.Readings.Add(reading);

                failed = true;
                handleShoeFailed(reading);
            }

            databaseAPI.SaveChanges();                            

        }//** end if reader not null
        else if (reader == null)
        {
            log("Received Msg from Unknown Reader: " + info.ReaderName + ", IP: " + info.IPAddress + ", MAC: " + info.MACAddress);
        }

    }//** end lock

    stopWatch.Stop();
    log("StopWatch: (" + stopWatch.Elapsed);
    log("- - - - - - -" + "\n");

}//** end msg handler

Is there another option?

TheColonel26
  • 2,618
  • 7
  • 25
  • 50
  • Show us some code; otherwise, any answer to your question would be merely speculative. Code will help us analyze the problem and possibly come up with a solution. We don't need the whole Bible, just a chapter or two. – Robert Harvey Jul 16 '14 at 22:11
  • OK my bad, I will in a little bit once I can sit down and rewrite my question. – TheColonel26 Jul 16 '14 at 22:13
  • 2
    Sorry, off topic, but: `lock("thisIsMyLock")` is bad juju. See: http://stackoverflow.com/questions/4192969/using-string-as-a-lock-to-do-thread-synchronization – CodingGorilla Jul 17 '14 at 02:33
  • @TheColonel26 I'm not sure what you mean by "cache the database". The EF context will only cache (and I use that term lightly) instances which it has previously loaded, and those instances **will not** reflect changes that were made to the database _outside_ of that context. So I'm not sure I quite understand what your requirements are here. Can you elaborate on this a little bit? – CodingGorilla Jul 17 '14 at 02:41
  • Oh I guess I miss understood something I read some where about the cache. I just assumed that's what it was doing since the method will always take a couple minutes to run the first time when it's using a non local context, but when the context is local it takes several minutes every time. I plan on having ASP MVC application eventually as a user interface for the data. The Web app would also have some simple options that can be set and communicated to the listening service via the shared database. The first one is a simple bool that tells the listen whether or not it should send an email. – TheColonel26 Jul 17 '14 at 02:56
  • 1
    I'd recommend running a SQL Profiler to see what your EF is really doing. Based on your code, it looks like a lot of round trips to the DB, which is part of what is slowing you down. My cursory review of your code seems like you're just recording "events", so essentially logging. If this is true, and each entry is not dependent on a previous entry, @Burt_Harris's answer is a good solution - fire off an SP that handles most of this for your, from creating the relationships to writing the various records. – CodeMonkey1313 Jul 17 '14 at 13:09

1 Answers1

2

I've inferred from you post that you are storing this information in a relational DB, probably MS SQL Server, and that the message rate is fairly high, and that the database size is large enough that retrieving the data from SQL into the dbcontext is slowing things down.

Given those, and the logic I see in the C#, I would suggest a significantly different approach: writing a SQL stored procedure to perform the logic in the section you've marked for improvement. The goal should be to have each message processed only requires one round-trip to the database server. My expectation would be that the stored procedure should be able to run in sub-second time, because SQL server itself is very good at caching.

This may require learning about transact-sql, but I suggest it would be a good investment of your time.

Burt_Harris
  • 6,415
  • 2
  • 29
  • 64
  • Thanks @Burt_Harris. After looking in to stored procedures more I've decided you're right this is definitely the route to go. It looks like I'm in for a steep learning curve initially. – TheColonel26 Jul 22 '14 at 13:56