我目前有一个有效的解决方案,但它非常混乱,并试图找到更好的方法来完成我的任务。
我有一个跨多个数据库使用的实体模型,但每个数据库都有一些表,这些表的模式是根据具体情况定制的。
如何根据数据库定义从运行时生成的动态类型中干净地实现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
进行比较,并根据需要更新已更改的属性并将其保存回数据库。