0

I am working in Windows forms application (using Entity Framework) and I have several tables in my database. These tables are similar (they have the same quantity of columns with the same column names), except that they (tables) have different names (it means, I have several entity classes).

On the firstForm (MainForm) I have a listbox, which contains all the table names I have. when the user selects one of them, the new form opens (addForm). On this form I have several textboxes to fill the table with data.

My problem is that I want to add data to the tables with universal method using Entity Framework. In general, if I have table called Customer with customerName and customerAge columns (and it corresponding class with the same name) I add data to it like this:

using(var context = new MyDbContext())
{
     var newCustomer = new Customer {customerName = "Alex", customerAge = "24"};
     context.Customers.Add(newCustomer);
     context.SaveChanges();
}

I don't want to write the code for every table in my database. I think that, when the item will be selected in the listbox, I have to pass the specific class name to constructor and then transform this name (type of string) into the specific class to add data to the specific table associated with this name.

How I can do this?

Another example - Supplier table:

using(var context = new MyDbContext())
{
     var newSupplier = new Customer { supplierName = "Bob", supplierAge =  "20" };
     context.Suppliers.Add(newSupplier);
     context.SaveChanges();
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
LOG
  • 188
  • 1
  • 12
  • You can get to the object properties using reflection, but how would you automatically identify for each class what the "name" is? And, does every class even have an "age" to fill in, too? In general, though, the answer would be "technically it can be done with reflection, but in practice this is really messy". – Nyerguds Mar 24 '18 at 17:27
  • I have this specific situation, where I have the same columns, listbox is filled exactly with the names of tables in my database. you mean, reflection can be used to create get the class from the string? – LOG Mar 24 '18 at 17:32
  • No, reflection can be used to get the list of all properties out of a type, get their name, type, attributes etc, and to fill them in dynamically. But there are indeed also ways to instantiate objects from the class name. Are you saying all your tables have identical columns layout? If so, please add more examples to your question. – Nyerguds Mar 24 '18 at 17:35
  • By the way, "I have listbox, which contains all the table names" - how is this list fetched/filled? Are these tables the actual entity classes, or just string names? Because if you have the classes, this all gets a load easier. – Nyerguds Mar 24 '18 at 17:39
  • I have names, not classes – LOG Mar 24 '18 at 18:04
  • I suggest you look into [this answer](https://stackoverflow.com/a/48815454/395685), where I used reflection to fill an object automatically. Specifically, the Reflection stuff is at `typeof(T).GetProperties()`. Though do note it may be tricky to find the property on the context object, since that is a plural, and not all plurals are just "putting an 's' behind the class name". To create objects from class names, look into `CreateInstance(className)` on the `Assembly` class, and/or `Type.GetType(string)`. – Nyerguds Mar 24 '18 at 18:10
  • Do note, all of this is _really_ advanced stuff, that breaks a _lot_ of the rules and conventions on how you _should_ be using the programming language. In general, you're _much_ better off just writing a small function or class per type. It'll also run much faster; Reflection is known to be rather slow. – Nyerguds Mar 24 '18 at 18:13
  • you mean, that it would be better to write specific methods for every table, yes? – LOG Mar 24 '18 at 18:16
  • Yes, indeed. This is pretty advanced stuff, and certainly not how I would handle database classes. – Nyerguds Mar 24 '18 at 18:41
  • 1
    keep in mind C# is a strongly typed language. If you want to deal with string to a concrete type conversion, other than JSON string deserialization to an object, it would hint at a "code smell". True, you could do reflection to solve this but why would you want to? I'm guessing you have a background in a different language where something like this might be appropriate but not in C# (not as a rule, at least). As @Nyerguds says, using reflection is fairly advanced stuff. – Toni Kostelac Mar 24 '18 at 18:55
  • I think all this [was already done for you before](https://archive.codeplex.com/?p=winformgenerator). – Gert Arnold Mar 25 '18 at 08:06

3 Answers3

1

If the logic is the same for all items, you could just make use of generics and do something like this:

public class Writer<T> where T:class
{
    private DbContext _context;
    public Writer(DbContext context)
    {
        _context = context;
    }

    public void Insert(T obj)
    {
        _context.Set<T>().Add(obj);
        _context.SaveChanges();
    }
}

You have to provide it with your EF context and specify which type you wish to use.

Hope this helps at least point you in the right direction.

Toni Kostelac
  • 351
  • 3
  • 17
  • where can I pass the string I received from listbox in this code? – LOG Mar 24 '18 at 18:05
  • @Beginner Inside obj. You would need to create a `Writer` and then you can call its `Insert()` method by passing as argument `Customer {supplierName = "Bob", supplierAge = "20"}`. – Edu Mar 24 '18 at 18:18
  • yes but first of all I have to create class from the string to paste it in Writer<>, isn't it right? – LOG Mar 24 '18 at 18:21
  • stop using strings and build up your object from the fields in the form and pass it down to the writer. – Toni Kostelac Mar 24 '18 at 18:24
  • Also, if you don't want to use the entity object, which is good practice, you could create a dto or similar and use something like AutoMapper to convert between the two objects. – Toni Kostelac Mar 24 '18 at 18:26
0

You can use reflection to accomplish this task:

Entity classes and context class:

public class Person1
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Person2
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MyContext : DbContext
{
    public MyContext() : base ("connection string") { }

    public DbSet<Person1> Person1 { get; set; }
    public DbSet<Person2> Person2 { get; set; }
}

Example program, which having database name can add given entity to it using reflection:

//I guess you can take this values from controls on your form
string database = "Person1";
int id = 1;
string name = "Charlie";
//get type of an entity
Type entityType = this.GetType().Assembly.GetTypes().Where(t => t.Name == database).First();
//geet object of an entity class
var entity = Activator.CreateInstance(entityType);
//set properties (name and id)
entity.GetType().GetProperty("Id").SetValue(entity, id);
entity.GetType().GetProperty("Name").SetValue(entity, name);
MyContext ctx = null;
try
{
    ctx = (MyContext)Activator.CreateInstance(typeof(MyContext));
    //here we'll loop thorugh properties of context (through databases),
    //if name match, add entity using reflection
    foreach (PropertyInfo property in ctx.GetType().GetProperties())
    {
        if (property.Name == database)
        {
            var dbSet = property.GetValue(ctx);
            //get Add method and invoke it with entity object
            dbSet.GetType().GetMethod("Add").Invoke(dbSet, new object[]{ entity });
            ctx.SaveChanges();
        }
    }
}
finally
{
    //always remeber to call Dispose method!
    if(ctx != null)
        ctx.Dispose();
}
Community
  • 1
  • 1
Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69
  • `Convert.ChangeType` does nothing in the case of `Convert.ChangeType(Activator.CreateInstance(entityType), entityType)`. The value returned by the Activator is already of the appropriate type (and its compile time type is `object`) – pinkfloydx33 Mar 24 '18 at 23:37
  • @pinkfloydx33 You are correct :) thanks for pointing that out. – Michał Turczyn Mar 25 '18 at 07:47
0

You can use DbContext.Database.ExecuteSqlCommand method:

public class Customer : Entity
{
}

public class Supplier : Entity
{
}

public void CreateEntity(string TableName, Entity entity)
{
    using(var context = new MyDbContext())
    {
        var name = new SqlParameter("@name", entity.customerName);
        var age = new SqlParameter("@age", entity.customerAge); 

        context.Database.ExecuteSqlCommand($"insert into {TableName} (customerName, customerAge) values (@name, @age)", 
             name, age);
    }
}
Slava Utesinov
  • 13,410
  • 2
  • 19
  • 26