1

I am a little bit confused about how to read data from Excel. I am trying to import Excel for updating product list, I create an Excel model; I added all basic properties like name, price, quantity, etc. into this model. I will read all Excel and map into this model. That's ok, then I will give this model to EF Core 5 to save to SQL Server.

public class ExcelModel
{ 
    public string Name { get; set }
    public int Price { get; set }
    public int Quantity { get; set }
}

I have a problem with product options. According to my DB schema, I have one table of products, one for options, one for option values, one for productOptionRelation.

enter image description here

Can you suggest another solution way or just solve on my way?

My colleges did this created field corresponding to values. like option1 and optionValue1, option2 and optionValue2 many of them, because each product could have many options. Model look like that, 20 option and 20 value was declared here and they manually map all these

the picture of bad model

For a temporary solution, I limited this option up to 5 and I created an list. and encapsulate all of them into list

public class ExcelOptionViewModel
{
    public string Option { get; set; }
    public string Value { get; set; }
}

This is my temp model, I encapsulated like that.

 public IList<ExcelOptionViewModel> OptionModels { get; set; } = new List<ExcelOptionViewModel>(); 

 public string Option1
 {
     get { return OptionModels[0].Option; } 
     set
     {
         this.OptionModels.Insert(0, new ExcelOptionViewModel { Option = value });
     }
 }

 public string Option1Value
 {
     get { return OptionModels[0].Value; }
     set { this.OptionModels[0].Value  = value; }
 }

This would be unlimited, You should enter how much you want

I have 2 solutions still I am researching one is, creating a method inside the excelviewmodel, this method will add all options and values into a list or I will use reflection, I am looking something like underlying type I will all option and values this underlying base type or something, when property loop came here, checking the type and assign all option1,option2,option3 or name like that properties to List<string> options, and same for the option values. I will use reading like option[0] and optionvalue[0]

Excel column names must be different because I read excel and turn it into datatable. Datatable column names must be different, it's not valid for reading into datatable

excel import wrong sample

excel import true sample

I used basically excel to data table function I can't remember but probably I found it in StackOverflow. Also, I added a feature there If some cell is null it will miss.

public List<T> ConvertDataTableToList<T>(DataTable dt)
{
    //datatable clomun names
    var columnNames = dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName.ToLower()).ToList();

    //selection properties equals to columnnames because I dont want loop for all props
    var properties = typeof(T).GetProperties().Where(prp => columnNames.Any(t => t.ToLower() == prp.Name.ToLower()));

    return dt.AsEnumerable().Select(row =>
    {
        var objT = Activator.CreateInstance<T>();
        foreach (var pro in properties)
        {
            try
            {
                if (row[pro.Name] != DBNull.Value)
                    pro.SetValue(objT, row[pro.Name], null);
                else
                    pro.SetValue(objT, null, null);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
        return objT;
    }).ToList();
}

I am looking something here when option1 or option2 comes here it would put this into a list

Also in my dt to model converter I dont want to use If but if some data value is null It throws an error which cant convert from dbnull value. If you have a suggest for it I would like release if condition :)

When All done I will map this excelviewmodel to product model something like this

 foreach (var prop in SideParams.columns)
            {
                var source = row.GetType().GetProperty(prop);

                var destination = product.GetType().GetProperty(prop);

                if (destination != null && source.GetValue(row) != null)
                {
                Type t = Nullable.GetUnderlyingType(destination.PropertyType) ?? destination.PropertyType;

                object safeValue = Convert.ChangeType(source.GetValue(row), t);
                destination.SetValue(product, safeValue);

            }
        }

I saw something here https://learn.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=net-6.0

it about binding flangs when reflecting model. "Specifies flags that control binding and the way in which the search for members and types is conducted by reflection." If there is way I can redirect option(1-2-3-4-5-6...) to list options

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • *how to read data from excel* can you. maybe **not** have this data in excel? There are better ways of creating a DB and driving data into it than having all the tables stacked side by side in a single tabular form – Caius Jard Dec 27 '21 at 13:15
  • @CaiusJard do not do that ... it's not helping the OP whatsoever – wiwi Dec 27 '21 at 13:25
  • there is many tables actually product is main table, option ,optionvalue, category, brand and etc. I did all, insert update and delete fucntions is work for all tables perfecly and optimized. But Only I didnt complete the options. only way is getting options from excel I have another idea but its is simple for us but its hard for users. only one option and optionvalue field on excel you will write your features splitted with ; when I read this I will split them and insert to productoptionrelation table. but its really hard for users – Çağlar Can Sarıkaya Dec 27 '21 at 13:34
  • the best for users is adding option1 and optionvalue1 and option2 optionvalue2 column I have tested this. – Çağlar Can Sarıkaya Dec 27 '21 at 13:35
  • 2
    @wiwi Do what? Query whether they made this rod for their own back? Did you see the line in the question where it says *You can suggest another solution way or just solve on my way.* - I'd love to suggest *another solution way* and I want to know how tied to parsing this file they truly are. Maybe its "my third party dinosaur supplier gives me this data and it's from a COBOL program that cannot be altered". Maybe it's "I designed this because I thought it would be a good idea".. Who knows? Let's ask.. – Caius Jard Dec 27 '21 at 13:35
  • What you need is an [EAV model](https://peter.lalovsky.com/2021/07/database/sql-server/t-sql/data-model-entity-atribute-value-eav/). This is in fact a SQL anti pattern, but it's the best you can do when entities have an unknown number of attributes and you want to store the data in a relational database. The alternative is NoSql. – Gert Arnold Dec 28 '21 at 09:01

1 Answers1

0

thanks for the help I solved my problem. If you need something like that, my solution is;

As you know OptionModels is what I created before, AddOptipns function is a new one I use for add data to list, The function work with the ref, otherwise it must be static, if I turn it static, option models also must be static, so I can't access the list.

 public IList<ExcelOptionViewModel> OptionModels { get; set; } = new List<ExcelOptionViewModel>();
 public void AddOptions(ref String option, ref String value)
     {
        OptionModels.Add(new ExcelOptionViewModel { Option = option.Trim(), Value = value.Trim() });
     }

And also add some new parts to convert model function,

calling that AddOptions method with reflection, I got an example from here https://learn.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=net-6.0

I was inspired by the swap example there.

public List<T> ConvertDataTableToList<T>(DataTable dt)
    {
        var columnNames = dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName.ToLower()).ToList();

        //selection properties equals to columnnames because I dont want loop for all props
        var type = typeof(T);
        var properties = type.GetProperties().Where(prp => columnNames.Any(t => t.ToLower() == prp.Name.ToLower())).ToList();

        var productOptions = columnNames.Where(x => x.Contains("option")).ToList() ?? new List<string>();

        return dt.AsEnumerable().Select(row =>
        {
            var objT = Activator.CreateInstance<T>();
            foreach (var pro in properties)
            {
                try
                {
                    if (row[pro.Name] != DBNull.Value)
                        pro.SetValue(objT, row[pro.Name], null);
                    else
                        pro.SetValue(objT, null, null);
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }

            for (var i = 0; i < productOptions.Count(); i += 2)
            {
                object[] argValues = new object[] { row[productOptions[i]].ToString(), row[productOptions[i + 1]].ToString() };
                String[] argNames = new String[] { "option", "value" } ;
                 
               var method =  type.GetMethod("AddOptions");
                method.Invoke(objT, argValues);
            }

            return objT;
        }).ToList();
    }

here is the added data :)

example of added data