<

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x

How to get a class out of a db query and serialize/deserialize it

Published on
10,088 Points
3,088 Views
Last Modified:
Approved
Sometimes it is useful to have a class that is a representation of an object.
The object may be an element of a list and the list itself can too.
When I query a db to get the fields of my object, I want to write them into a class.
This is the way I do:

1. Base class for Entity

This step will enable you to better code writing and clarity.
[Serializable]
public class BaseEntity
{
    public BaseEntity() { }
}

Open in new window

I will add later some extension method that will give further help.

2. Base class for Entity List

This class is the list of typed objects
[Serializable]
public class BaseEntityList<TEntity> : List<TEntity>
    where TEntity : BaseEntity
{
    public Int32 VirtualItemCount;

    public BaseEntityList()
    {
    }

    public BaseEntityList(Int32 capacity)
        : base(capacity)
    {
    }

    public BaseEntityList(IEnumerable<TEntity> collection)
        : base(collection)
    {
    }

    public virtual void FromEntityArray(BaseEntity[] entityArray)
    {
        Clear();
        AddRange((TEntity[])entityArray);
    }

    public virtual void FromIEnumerable(IEnumerable<TEntity> entityList)
    {
        Clear();
        AddRange((TEntity[])entityList);
    }
}

Open in new window

Please note that I added different constructors and some methods that can become useful (but maybe you'll never use them).

3. Create the class for your object (Entity)

[Serializable]
public class record : BaseEntity
{
    public record() { }

    public record(IDataReader dr)
    {
        PopulateIndexes(dr);

        prefix = dr.GetString(index_prefix);
        title = dr.GetString(index_title);
        document_id = dr.GetInt32(index_document_id);
        revision = dr.GetString(index_revision);
        class_id = dr.GetInt32(index_class_id);
        name = dr.GetString(index_name);
        description = dr.IsDBNull(index_description) ? null : dr.GetString(index_description);
    }

    public String prefix { get; set; }
    public String title { get; set; }
    public Int32 document_id { get; set; }
    public String revision { get; set; }
    public Int32 class_id { get; set; }
    public String name { get; set; }
    public String description { get; set; }

    #region Indexes
    private static Int32 index_prefix = -1;
    private static Int32 index_title = -1;
    private static Int32 index_document_id = -1;
    private static Int32 index_revision = -1;
    private static Int32 index_class_id = -1;
    private static Int32 index_name = -1;
    private static Int32 index_description = -1;

    private void PopulateIndexes(IDataReader dr)
    {
        if (index_prefix == -1)
        {
            index_prefix = dr.GetOrdinal("prefix");
            index_title = dr.GetOrdinal("title");
            index_document_id = dr.GetOrdinal("document_id");
            index_revision = dr.GetOrdinal("revision");
            index_class_id = dr.GetOrdinal("class_id");
            index_name = dr.GetOrdinal("name");
            index_description = dr.GetOrdinal("description");
        }
    }
    #endregion
}

Open in new window

Note that I chose to read all the field indexes and keep them in static variables. This can improve performances when you have many records to read, because the DataReader will point to the column index, rather than reading it each time.

I will suggest the use of "PopulateIndexes" and I also suggest to ad a "reset" method that will be called in the next step:
public static void ResetIndexes()
{
    index_prefix = -1;
    index_title = -1;
    index_document_id = -1;
    index_revision = -1;
    index_class_id = -1;
    index_name = -1;
    index_description = -1;
}

Open in new window

This method should be public.

4. Create the class that represents the list of your Entity

[Serializable]
public class records : BaseEntityList<record>
{
    public records() { }

    public records(IDataReader dr)
    {
        record.ResetIndexes();

        while (dr.Read())
            Add(new record(dr));
    }
}

Open in new window

This is essentially a List of objects and I put a constructor with a IDataReader (you may pass to it a SqlDataReader or other types of DataReader that inherit from IDataReader).
This constructor starts with the ResetIndexes method. Why?
Well, you may find that if you get your Entity from different queries or sources, the field order may vary. Resetting the indexes will force the PopulateIndexes method to reload them.
Since the reset/reload operation will be done once for every List (usually for every query execution), I consider it as performance-wise.

The classes for "record" and "records" entities are now ready to be used.
You can attach the "records" class to your query execution, so you can get fully typed objects out of your db.


But let dig a bit more deep...


5. Serialize an entity (or entity-list) to XML

This is a very useful step, that can let you do more with your entities.
There are situations where you better work with simple strings, rather than complex objects like your entity / entity-list.
For example, when your client application will interface your remote systems by using WebServices or similar. Your WebService may return an entity serialized as XML string for simplicity of use (and we later see other purposes).

This is a very simple example of how to serialize an Entity (class) to a XML string:
private String SerializeEntityXML<T>(T Entity)
{
    using (MemoryStream ms = new MemoryStream())
    {
        new System.Xml.Serialization.XmlSerializer(typeof(T)).Serialize(ms, Entity);
        ms.Position = 0;

        using (StreamReader sr = new StreamReader(ms))
            return sr.ReadToEnd();
    }
}

Open in new window

6. Deserialize the string to Entity

This is as simple as the serialization method:
private T DeserializeEntityXML<T>(String SerializedEntity)
{
    using (MemoryStream memoryStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(SerializedEntity)))
    {
        T retValue = default(T);
        memoryStream.Position = 0;

        retValue = (T)new System.Xml.Serialization.XmlSerializer(typeof(T)).Deserialize(memoryStream);
        return retValue;
    }
}

Open in new window


But how can we use them?
I just wrote a sample method to test the Entity classes creation and the serialization/deserialization methods:
private void Test()
{
    records r = null;
    using (System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings["sqlConnectionString"].ConnectionString))
    {
        conn.Open();
        using (System.Data.SqlClient.SqlCommand comm = new System.Data.SqlClient.SqlCommand()
                                                                    {
                                                                        Connection = conn,
                                                                        CommandText = "select '1234' as prefix,'Document 1' as title,946 as document_id,'1.1' as revision,24 as class_id,'Type 01' as name,null as description union select '1234' as prefix,'Document 2' as title,947 as document_id,'1.1' as revision,24 as class_id,'Type 01' as name,null as description",
                                                                        CommandType = CommandType.Text
                                                                    })
        {
            r = new records(comm.ExecuteReader());
        }
    }

    String serialized = SerializeEntityXML<records>(r);
    File.WriteAllText(@"c:\temp\records.xml", serialized, System.Text.Encoding.UTF8);
    records r_deserialized = DeserializeEntityXML<records>(File.ReadAllText(@"c:\temp\records.xml", System.Text.Encoding.UTF8));
}

Open in new window


The "serialized" variable will contain this XML string:
<?xml version="1.0"?>
<ArrayOfRecord xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <record>
    <prefix>1234</prefix>
    <title>Document 1</title>
    <document_id>946</document_id>
    <revision>1.1</revision>
    <class_id>24</class_id>
    <name>Type 01</name>
  </record>
  <record>
    <prefix>1234</prefix>
    <title>Document 2</title>
    <document_id>947</document_id>
    <revision>1.1</revision>
    <class_id>24</class_id>
    <name>Type 01</name>
  </record>
</ArrayOfRecord>

Open in new window






I will now point you to an interesting use of the serialization / deserialization.

I have been in trouble when I needed to transfer very large data from server to clients, over tiny connections.
You may say that I could have used asynchronous calls, or streaming data from server.
But those will not solve the problem of the time needed to transfer. The data is large, but the connection is limited. So much time is needed...

But after the serialization of the entities to string, I had the idea of compressing the string content. So a serialized object of about 2 MB of data could be "zipped" to just a few KB in a very little time.
And the deserialization too is not much time-consuming.

You will wonder about how much time can be saved by using this method.

7. Compress your Entity

So I added SharpZipLib to my project's references and started developing some code to manage this idea.
Please note that you may use the compression library you prefer.
I just use this because it is fast enough for my needs while having a good compression level.

This is the compression method:
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;

private static String CompressToBase64String(String inputString, Encoding encoding, String typeName)
{
#if DEBUG
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();
#endif

    Byte[] DataToCompress = encoding.GetBytes(inputString);
    using (MemoryStream msOut = new MemoryStream())
    {
        using (DeflaterOutputStream z = new DeflaterOutputStream(msOut))
            z.Write(DataToCompress, 0, DataToCompress.Length);

        Byte[] byteArr = msOut.ToArray();

#if DEBUG
        stopWatch.Stop();
        Debug.WriteLine(String.Format("Entity '{0}' compression in {1} milliseconds. Original size: {2} bytes. Compressed size: {3} bytes (ratio: {4}%).", typeName, stopWatch.ElapsedMilliseconds, DataToCompress.Length, byteArr.Length, Math.Round((Decimal)(byteArr.Length * 100 / DataToCompress.Length), 2)));
#endif

        return Convert.ToBase64String(byteArr);
    }
}

Open in new window

I left 2 conditional-compile sections where you can see in your Output window how much you saved by using compression.
If you don't need it, you can strip away the "typeName" parameter from the method.

We can now replace the "SerializeEntityXML" (seen in step 5) method to:
internal static String SerializeEntityXML<T>(T Entity, Boolean Compress)
{
    Type t = typeof(T);

    try
    {
        using (MemoryStream ms = new MemoryStream())
        {
            new System.Xml.Serialization.XmlSerializer(t).Serialize(ms, Entity);
            ms.Position = 0;

            using (StreamReader sr = new StreamReader(ms))
            {
                String serializedString = sr.ReadToEnd();

                if (Compress)
                    return CompressToBase64String(serializedString, System.Text.Encoding.UTF8, t.ToString());
                else
                    return serializedString;
            }
        }
    }
    catch (Exception ex)
    {
        throw new Exception("Error during entity serialization.", ex);
    }
}

Open in new window

Now you can call the serialization method with the new parameter set as "true".
In this example (of just 2 elements) you will not gain a great compression:
Entity 'records' compression in 14 milliseconds. Original size: 563 bytes. Compressed size: 224 bytes (ratio: 39%).

But it will be impressive for classes with larger content.
This is the debug output when the list is of 56 elements:
Entity 'records' compression in 14 milliseconds. Original size: 11581 bytes. Compressed size: 399 bytes (ratio: 3%).

This is a 11 KB to 0.4 KB compression!!!

7. Decompress your Entity

As for the compression method:
private static Byte[] DecompressFromBase64String(String SerializedEntity)
{
    Byte[] buffer = null;
    using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(SerializedEntity)))
    {
        ms.Position = 0;
        using (InflaterInputStream z = new InflaterInputStream(ms))
        {
            using (MemoryStream unzipped = new MemoryStream())
            {
                Int32 size;
                Byte[] b = new Byte[4096];
                while ((size = z.Read(b, 0, b.Length)) > 0)
                    unzipped.Write(b, 0, size);

                if (unzipped.Length == 0)
                {
                    Debug.WriteLine(String.Format("Error while decompressing string: '{0}'. Output is empty string.", SerializedEntity));
                    throw new Exception(String.Format("Error while decompressing string: '{0}'. Output is empty string.", SerializedEntity));
                }

                unzipped.Position = 0;
                buffer = unzipped.GetBuffer();
            }
        }
    }
    return buffer;
}

Open in new window

And, as before, you can change the DeserializeXML method:
internal static T DeserializeEntityXML<T>(String SerializedEntity, Boolean IsCompressed)
{
    Type t = typeof(T);

    if (String.IsNullOrWhiteSpace(SerializedEntity))
    {
        Debug.WriteLine(String.Format("Error in DeserializeEntity<{0}> -- Input string is empty", t.ToString()));
        throw new Exception(String.Format("Error in DeserializeEntity<{0}> -- Input string is empty", t.ToString()));
    }

#if DEBUG
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();
    Int64 DecompressionMilliseconds = 0;
#endif

    Byte[] buffer = null;
    if (IsCompressed)
    {
        buffer = DecompressFromBase64String(SerializedEntity);
#if DEBUG
        DecompressionMilliseconds = stopWatch.ElapsedMilliseconds;
#endif
    }
    else
        buffer = System.Text.Encoding.UTF8.GetBytes(SerializedEntity);

    using (MemoryStream memoryStream = new MemoryStream(buffer))
    {
        T retValue = default(T);
        memoryStream.Position = 0;

        retValue = (T)new System.Xml.Serialization.XmlSerializer(t).Deserialize(memoryStream);

#if DEBUG
        stopWatch.Stop();
        Debug.WriteLine(String.Format("Entity '{0}' XML deserialization in {1} milliseconds{2}", t.ToString(), stopWatch.ElapsedMilliseconds, IsCompressed ? String.Format(", ({0} of wich were used for decompression).", DecompressionMilliseconds) : "."));
#endif
        return retValue;
    }
}

Open in new window

And this is the output from the decompression method:
Entity 'records' XML deserialization in 13 milliseconds, (9 of wich were used for decompression).
We have seen how to:
- get data from a db
- put it into Entity and EntityList classes
- serialize the entities
- deserialize the entities
- compress the serialized entity to string
- decompress that string to entity


Now we can just let things be more useful, by adding some ExtensionMethods to our BaseEntity:
public static class BaseEntity_Extensions
{
    public static String Serialize<T>(this T t, Boolean Compress)
    {
        return BaseEntity_Methods.SerializeEntityXML(t, Compress);
    }

    public static T Deserialize<T>(this T t, String SerializedEntity, Boolean IsCompressed)
    {
        return t = BaseEntity_Methods.DeserializeEntityXML<T>(SerializedEntity, IsCompressed);
    }
}

Open in new window

Please note that I am pointing to a "BaseEntity_Methods", that is the class where I have put the above serialization/deserialization methods.

This way you can serialize/deserialize anything that you derived from BaseEntity or BaseEntityList in a very simple way:
String serialized = r.SerializeXML(true);
File.WriteAllText(@"c:\temp\records.xml", serialized, System.Text.Encoding.UTF8);
records r_deserialized = new records().DeserializeXML(File.ReadAllText(@"c:\temp\records.xml", System.Text.Encoding.UTF8), true);

Open in new window









Finally, I would like to attach a fully functional test solution, but I am not able to attach a .zip file of the solution, because of not allowed extensions in the archive.
So you can:
- Create a new Windows Forms project in your VS
- Add SharpZipLib as reference
- Replace the code of Form1.cs with this (but keep your namespace):
    using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Diagnostics;
    using System.IO;
    using System.Text;
    using System.Windows.Forms;
    using System.Xml.Serialization;

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Test()
        {
            // BREAKPOINT HERE....
            String Connectionstring = textBox1.Text;

            records r = null;
            using (System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection(Connectionstring))
            {
                conn.Open();
                using (System.Data.SqlClient.SqlCommand comm = new System.Data.SqlClient.SqlCommand()
                                                                            {
                                                                                Connection = conn,
                                                                                CommandText = "select '1234' as prefix,'Document 1' as title,946 as document_id,'1.1' as revision,24 as class_id,'Type 01' as name,null as description union select '1234' as prefix,'Document 2' as title,947 as document_id,'1.1' as revision,24 as class_id,'Type 01' as name,null as description",
                                                                                CommandType = CommandType.Text
                                                                            })
                {
                    r = new records(comm.ExecuteReader());
                }
            }

            String serialized = r.SerializeXML(true);
            File.WriteAllText(@"c:\temp\records.xml", serialized, System.Text.Encoding.UTF8);
            records r_deserialized = new records().DeserializeXML(File.ReadAllText(@"c:\temp\records.xml", System.Text.Encoding.UTF8), true);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Test();
        }
    }

    #region "Record and Records" Entities
    [Serializable]
    public class records : BaseEntityList<record>
    {
        public records() { }

        public records(IDataReader dr)
        {
            record.ResetIndexes();

            while (dr.Read())
                Add(new record(dr));
        }
    }

    [Serializable]
    public class record : BaseEntity
    {
        public record() { }

        public record(IDataReader dr)
        {
            PopulateIndexes(dr);

            prefix = dr.GetString(index_prefix);
            title = dr.GetString(index_title);
            document_id = dr.GetInt32(index_document_id);
            revision = dr.GetString(index_revision);
            class_id = dr.GetInt32(index_class_id);
            name = dr.GetString(index_name);
            description = dr.IsDBNull(index_description) ? null : dr.GetString(index_description);
        }

        public String prefix { get; set; }
        public String title { get; set; }
        public Int32 document_id { get; set; }
        public String revision { get; set; }
        public Int32 class_id { get; set; }
        public String name { get; set; }
        public String description { get; set; }

        #region Indexes
        private static Int32 index_prefix = -1;
        private static Int32 index_title = -1;
        private static Int32 index_document_id = -1;
        private static Int32 index_revision = -1;
        private static Int32 index_class_id = -1;
        private static Int32 index_name = -1;
        private static Int32 index_description = -1;

        private void PopulateIndexes(IDataReader dr)
        {
            if (index_prefix == -1)
            {
                index_prefix = dr.GetOrdinal("prefix");
                index_title = dr.GetOrdinal("title");
                index_document_id = dr.GetOrdinal("document_id");
                index_revision = dr.GetOrdinal("revision");
                index_class_id = dr.GetOrdinal("class_id");
                index_name = dr.GetOrdinal("name");
                index_description = dr.GetOrdinal("description");
            }
        }

        public static void ResetIndexes()
        {
            index_prefix = -1;
            index_title = -1;
            index_document_id = -1;
            index_revision = -1;
            index_class_id = -1;
            index_name = -1;
            index_description = -1;
        }
        #endregion
    }
    #endregion


    #region BaseEntity
    [Serializable]
    public class BaseEntity
    {
        public BaseEntity() { }
    }

    public static class BaseEntity_Extensions
    {
        public static String SerializeXML<T>(this T t, Boolean Compress)
        {
            return BaseEntity_Methods.SerializeEntityXML(t, Compress);
        }

        public static T DeserializeXML<T>(this T t, String SerializedEntity, Boolean IsCompressed)
        {
            return t = BaseEntity_Methods.DeserializeEntityXML<T>(SerializedEntity, IsCompressed);
        }
    }
    #endregion

    #region BaseEntityList
    [Serializable]
    public class BaseEntityList<TEntity> : List<TEntity>
        where TEntity : BaseEntity
    {
        public Int32 VirtualItemCount;

        public BaseEntityList()
        {
        }

        public BaseEntityList(Int32 capacity)
            : base(capacity)
        {
        }

        public BaseEntityList(IEnumerable<TEntity> collection)
            : base(collection)
        {
        }

        public virtual void FromEntityArray(BaseEntity[] entityArray)
        {
            Clear();
            AddRange((TEntity[])entityArray);
        }

        public virtual void FromIEnumerable(IEnumerable<TEntity> entityList)
        {
            Clear();
            AddRange((TEntity[])entityList);
        }
    }
    #endregion

    #region BaseEntity_Methods
    internal static class BaseEntity_Methods
    {
        private static String CompressToBase64String(String inputString, Encoding encoding, String typeName)
        {
#if DEBUG
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
#endif

            Byte[] DataToCompress = encoding.GetBytes(inputString);
            using (MemoryStream msOut = new MemoryStream())
            {
                using (DeflaterOutputStream z = new DeflaterOutputStream(msOut))
                    z.Write(DataToCompress, 0, DataToCompress.Length);

                Byte[] byteArr = msOut.ToArray();

#if DEBUG
                stopWatch.Stop();
                Debug.WriteLine(String.Format("Entity '{0}' compression in {1} milliseconds. Original size: {2} bytes. Compressed size: {3} bytes (ratio: {4}%).", typeName, stopWatch.ElapsedMilliseconds, DataToCompress.Length, byteArr.Length, Math.Round((Decimal)(byteArr.Length * 100 / DataToCompress.Length), 2)));
#endif

                return Convert.ToBase64String(byteArr);
            }
        }

        private static Byte[] DecompressFromBase64String(String SerializedEntity)
        {
            Byte[] buffer = null;
            using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(SerializedEntity)))
            {
                ms.Position = 0;
                using (InflaterInputStream z = new InflaterInputStream(ms))
                {
                    using (MemoryStream unzipped = new MemoryStream())
                    {
                        Int32 size;
                        Byte[] b = new Byte[4096];
                        while ((size = z.Read(b, 0, b.Length)) > 0)
                            unzipped.Write(b, 0, size);

                        if (unzipped.Length == 0)
                        {
                            Debug.WriteLine(String.Format("Error while decompressing string: '{0}'. Output is empty string.", SerializedEntity));
                            throw new Exception(String.Format("Error while decompressing string: '{0}'. Output is empty string.", SerializedEntity));
                        }

                        unzipped.Position = 0;
                        buffer = unzipped.GetBuffer();
                    }
                }
            }
            return buffer;
        }

        #region Serialization / Deserialization methods
        #region XML serializer/deserializer
        internal static String SerializeEntityXML<T>(T Entity, Boolean Compress)
        {
            Type t = typeof(T);

            try
            {
#if DEBUG
                Stopwatch stopWatch = new Stopwatch();
                stopWatch.Start();
#endif

                using (MemoryStream ms = new MemoryStream())
                {
                    new XmlSerializer(t).Serialize(ms, Entity);

                    // Reposition the stream cusror to the first byte.
                    ms.Position = 0;

                    using (StreamReader sr = new StreamReader(ms))
                    {
                        String serializedString = sr.ReadToEnd();

#if DEBUG
                        stopWatch.Stop();
                        Debug.WriteLine(String.Format("Entity '{0}' XML serialization in {1} milliseconds. Serialized string length: {2}", t.ToString(), stopWatch.ElapsedMilliseconds, serializedString.Length));
#endif

                        if (Compress)
                            return BaseEntity_Methods.CompressToBase64String(serializedString, Encoding.UTF8, t.ToString());
                        else
                            return serializedString;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Error during entity serialization.", ex);
            }
        }

        internal static T DeserializeEntityXML<T>(String SerializedEntity, Boolean IsCompressed)
        {
            Type t = typeof(T);

            if (String.IsNullOrWhiteSpace(SerializedEntity))
            {
                Debug.WriteLine(String.Format("Error in DeserializeEntity<{0}> -- Input string is empty", t.ToString()));
                throw new Exception(String.Format("Error in DeserializeEntity<{0}> -- Input string is empty", t.ToString()));
            }

#if DEBUG
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            Int64 DecompressionMilliseconds = 0;
#endif

            Byte[] buffer = null;
            if (IsCompressed)
            {
                buffer = DecompressFromBase64String(SerializedEntity);
#if DEBUG
                DecompressionMilliseconds = stopWatch.ElapsedMilliseconds;
#endif
            }
            else
                buffer = Encoding.UTF8.GetBytes(SerializedEntity);

            using (MemoryStream memoryStream = new MemoryStream(buffer))
            {
                T retValue = default(T);
                memoryStream.Position = 0;

                retValue = (T)new XmlSerializer(t).Deserialize(memoryStream);

#if DEBUG
                stopWatch.Stop();
                Debug.WriteLine(String.Format("Entity '{0}' XML deserialization in {1} milliseconds{2}", t.ToString(), stopWatch.ElapsedMilliseconds, IsCompressed ? String.Format(", ({0} of wich were used for decompression).", DecompressionMilliseconds) : "."));
#endif
                return retValue;
            }
        }
        #endregion
        #endregion
    }
    #endregion

Open in new window


And it should work... Just put a breakpoint inside the "Test" method, write your connection string into the textbox and click "test"...


Comments and contributions are welcome.
If you find this article good for your work, please set it as "helpful".
You are free to use any part of this code, but I would be pleased if you link this page.

Have a nice coding...
0
Comment
Author:jonnidip
0 Comments

Featured Post

Become a CompTIA Certified Healthcare IT Tech

This course will help prep you to earn the CompTIA Healthcare IT Technician certification showing that you have the knowledge and skills needed to succeed in installing, managing, and troubleshooting IT systems in medical and clinical settings.

Join & Write a Comment

Did you know PowerShell can save you time with SaaS platforms? Simply leverage RESTfulAPIs to build your own PowerShell modules. These will kill repetitive tickets and tabs, using the command Invoke-RestMethod. Tune into this webinar to learn how…
Learn the basics of Skype For Business in office 365

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month