使用实体框架的MVC模型绑定动态类型

时间:2017-07-02 22:39:30

标签: c# asp.net-mvc entity-framework

我目前有一个有效的解决方案,但它非常混乱,并试图找到更好的方法来完成我的任务。

我有一个跨多个数据库使用的实体模型,但每个数据库都有一些表,这些表的模式是根据具体情况定制的。

如何根据数据库定义从运行时生成的动态类型中干净地实现MVC模型绑定?

简而言之,在我的工作,但凌乱的代码,我有一个模型(简化时)看起来像这样:

public class Actor
{
    [Key]
    public int ID { get; set; }
    [MaxLength(100)]
    public string FirstName { get; set; }
    [MaxLength(100)]
    public string LastName { get; set; }
    public UserDefinedFields Udfs { get; set; }
}

public class UserDefinedFields
{
    [Key]
    public int ID { get; set; }
    public int ActorId {get; set;}
}

在我的应用程序中,有多个数据库,并且对于每个数据库,用户定义的字段具有不同的模式。

例如:

Create Table [Database1].[dbo].[UserDefinedFields]
(
    [ID] [int] IDENTITY(1,1) NOT NULL PRIMARY_KEY,
    [ActorId] [int] NOT NULL REFERENCES ACTOR(ID),
    [PurchaserID] [varchar](50) NULL,
    [BrandName] [varchar](100) NULL,
)

Create Table [Database2].[dbo].[UserDefinedFields]
(
    [ID] [int] IDENTITY(1,1) NOT NULL PRIMARY_KEY,
    [ActorId] [int] NOT NULL REFERENCES ACTOR(ID),
    [TotalUnits] [int] NULL,
    [TotalPriceRounded] [int] NULL,
)

我的控制器将ActorViewModel作为与IModelBinder绑定的参数:

[HttpPost]
public ActionResult Form(int CaseId, Guid FormId, [ModelBinder(typeof(ActorModelBinder))] ActorViewModel actorViewModel)
{
    // Validation logic.
    actorViewModel.Save();
    return View(actorViewModel);
}

现在我的ActorModelBinder我必须开始跳过篮球。

public class CASClaimViewModelBinder : System.Web.Mvc.IModelBinder
{
    public object BindModel(ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext)
    {
        IValueProvider provider = bindingContext.ValueProvider;
        ActorViewModel model = new ActorViewModel();
        BindModel();
        return model;
    }
}

ActorViewModel的构造函数中,创建了从DynamicDbContext派生的System.Data.Entity.DbContext

DynamicDbContext ctx = null;
UserDefinedFields udfs =null;

public ActorViewModel()
{
   ctx = DbContextFactory.GetContext();
   udfs = ctx.CreateUserDefinedFieldsInstance();  
}

DynamicDbContext源自System.Data.Entity.DbContext,引擎盖DbContextFactory.GetContext加载基础EDMX模型,并将基本模型中的类型添加到DynamicDbContext

DynamicDbContext实际上是我创建的类库的半大型站点的一部分,它有许多类,类型构建器和其他方法来动态读取任何给定的EDMX模型和动态编译和实体模型。它基本上是在实体框架的一部分上重新发明轮子,这样它不会一次生成静态实体模型,而是在运行时从任何数据库动态创建一个实体模型,给它一个连接字符串。

因此,它连接到数据库并读取该数据库的自定义UserDefinedFields表的列定义,以及我在EDMX模型中标记为DynamicEntity的任何其他表。 / p>

然后我使用System.Reflection.Emit.TypeBuilder动态创建动态UserDefinedFields类型,并将其添加到DynamicDbContext

ctx.CreateUserDefinedFieldsInstance()创建由类型构建器创建的动态UserDefinedFields类型的实例并将其返回。

BindModel()方法中,我需要创建一个List<ModelBindingTarget>并遍历每个方法,并手动将我的模型的属性绑定到Request.Form中的值。

List<ModelBindingTarget> bindingTargets = new List<ModelBindingTarget>()
{
    new ModelBindingTarget() { TargetType = typeof(Actor), Prefix= Constants.ActorPrefix , Instance = model.Actor } ,
    new ModelBindingTarget() { TargetType = model.Claim.UDFs.GetType(),  Prefix= Constants.UDFPrefix, Instance = model.Actor.UDFs  } ,
    // actual model is much more complex and all of the custom types in the model need to be bound.
};
bindingTargets.ForEach(x => BindFromForm(x));

BindFromForm循环遍历Request.Form中的所有密钥。 ForEach其中一个键检查模型中是否存在属性,其名称和路径与键匹配,当找到与表单键匹配的属性名称时,它会设置属性。

foreach (ModelBindingTarget target in bindingTargets)
{
    string prefix = target.Prefix;

    if (!target.TargetType.IsGenericType)
    {
        foreach (System.Reflection.PropertyInfo prop in target.Instance.GetType().GetProperties())
        {
            string formkey = prefix + prop.Name;
            var val = provider.GetValue(formkey);

            if (val != null && val.AttemptedValue != null)
            {
                try
                {
                    if (prop.PropertyType == typeof(bool) || prop.PropertyType == typeof(bool?))
                    {
                        if (val.AttemptedValue == Constants.On || val.AttemptedValue == Constants.One || string.Compare(val.AttemptedValue, Constants.True, true) == 0 || string.Compare(val.AttemptedValue, Constants.Y, true) == 0)
                        {
                            prop.SetValue(target.Instance, true);
                        }
                        else if (val.AttemptedValue == Constants.Zero || string.Compare(val.AttemptedValue, Constants.False, true) == 0 || string.Compare(val.AttemptedValue, Constants.N, true) == 0)
                        {
                            prop.SetValue(target.Instance, false);
                        }
                        else
                        {
                            prop.SetValue(target.Instance, val.ConvertTo(prop.PropertyType));
                        }
                    }
                    else
                    {
                        prop.SetValue(target.Instance, val.ConvertTo(prop.PropertyType));
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Could not value of '" + val.AttemptedValue + "' for" + formkey + " to type " + prop.PropertyType.ToString(), ex);
                }
            }
        }
    }
    else
    {
        List<int> indexes = new List<int>();

        foreach (string key in Form.Keys)
        {
            if (key.StartsWith(prefix))
            {
                int index = Convert.ToInt32(key.Substring(prefix.Length).Split("]".ToCharArray())[0]);

                if (!indexes.Contains(index))
                {
                    indexes.Add(index);
                }
            }
        }

        if (indexes.Count == 0) 
            continue;

        indexes.Sort();
        Type InstanceType = null;
        string instanceTypeName = target.Instance.GetType().Name;

        if (instanceTypeName == "TransactionDetails")
        {
            InstanceType = typeof(TransactionDetail);
        }
        else if (instanceTypeName == "UserDefinedRow")
        {
            InstanceType = model.GetUDRType();
        }
        else
        {
            InstanceType = model.GetTypeFromGeneric(target);
        }

        System.Collections.IList list = target.Instance as System.Collections.IList;

        foreach (int index in indexes)
        {
            object instance = Activator.CreateInstance(InstanceType);

            foreach (System.Reflection.PropertyInfo prop in InstanceType.GetProperties())
            {
                string formkey = prefix + index.ToString() + "]." + prop.Name;
                var val = provider.GetValue(formkey);

                if (val != null && val.AttemptedValue != null)
                {
                    if (prop.PropertyType == typeof(bool) || prop.PropertyType == typeof(bool?))
                    {
                        if (val.AttemptedValue == Constants.On || val.AttemptedValue == Constants.One || string.Compare(val.AttemptedValue, Constants.True, true) == 0 || string.Compare(val.AttemptedValue, Constants.Y, true) == 0)
                        {
                            prop.SetValue(instance, true);
                        }
                        else if (val.AttemptedValue == Constants.Zero || string.Compare(val.AttemptedValue, Constants.False, true) == 0 || string.Compare(val.AttemptedValue, Constants.N, true) == 0)
                        {
                            prop.SetValue(instance, false);
                        }
                        else
                        {
                            prop.SetValue(instance, val.ConvertTo(prop.PropertyType));
                        }
                    }
                    else
                    {
                        prop.SetValue(instance, val.ConvertTo(prop.PropertyType));
                    }
                }
            }

            list.Add(instance);
        }
    }
}   

现在,如果这不足以让事情变得更糟,那就是更新和现有实体需要更多的箍。

需要将绑定模型与现有DbEntity进行比较,并根据需要更新已更改的属性并将其保存回数据库。

0 个答案:

没有答案