EntityFramework 如何在数据库优先模式下识别复杂类型

6
我们正在使用EntityFramework,并已经通过Database First进行了漫长的旅程。我们有多个上下文和许多实体。虽然我们考虑采用Code First项目,但我们想探索EF可用的替代可能性。
我们正在使用当前最新版本的EF,即6.0.2
我们有很多具有共同字段(如审核字段“CreatedBy”,“CreatedDate”,“UpdatedBy”和“UpdatedDate”)的表,我们认为在这里使用ComplexType是完美的选择。
在使用这些字段的表中,当我们从数据库生成代码时,它会以原始形式提取这些字段。然后,我们在模型浏览器中删除它,添加复杂类型,然后将复杂属性映射到DB中的字段名称。
在这个实验中,我们运行了“从模型生成数据库”,而没有映射字段,以查看生成的列名以及是否有任何约定或自动魔法可以双向执行此操作(将复杂类型转换为列并将列识别为复杂类型)。
结果是列名带有下划线和字段名的复杂类型后缀。我们进行了测试,并且当我们重新生成模型时,它没有从数据库中提取出复杂类型。
有没有一种适当的方法让EF在使用数据库优先时检测到复杂类型?有没有我可以编写的代码工厂、构建器、策略或模板?
我们的主要动机是我们有相当数量的表需要支持,我们接受频繁的更改,我们希望防止我们团队中的人忽略这一步并破坏代码库。
非常感谢您的时间StackOverflowians!
- 编辑 -
虽然这不能解决自动检测复杂类型的问题,但已经解决了开发人员每次运行T4模板更新时破坏模型的问题。

http://www.ninjacrab.com/2015/02/07/update-entityframework-detecting-complex-type-with-database-first/


你是在建议使用复杂类型来简化审计的使用吗?另外,我对你的审计列名很好奇。CreatedBy 是一个 varchar 吗?如果不是,而实际上是一个 Id 指针,那么我建议使用 CreatedById。我喜欢 CreatedBy 的可读性,但 CreatedDate 听起来有点老派,你可以考虑使用 CreatedOn(我不喜欢匈牙利命名法)。 - Erik Philips
是的,我们使用复杂类型来简化审计。完全开放其他实践/想法。我们的“By”字段是varchar而不是FK/指针ID,因为在我们的用例中,我们实际上允许对参考数据进行硬删除。每次写入我们的主表时,它实际上会向辅助“审计”表写入记录。虽然实际表可能(并且可能应该)实际上具有对主用户的FK,但审计表不强制引用,以便我们可以维护一个轨迹。最后,我同意“On”的观点,只是以前没有听过这个建议,也没有人真正抱怨 :) - Min
"Used",现在已经不再使用,正如帖子所描述的那样。 - Min
1个回答

1
我所做的是通过EF实现的一种通用的半仓储库模式。我还使用了接口来允许标识和自动使用方法(如果它们匹配)。
可审计接口:
public interface IDbAuditable : IDbEntity
{
    Guid CreatedById { get; set; }
    DateTime CreatedOn { get; set; }
    Guid ModifiedById { get; set; } 
    DateTime ModifiedOn { get; set; }
    Guid? DeletedById { get; set; } 
    DateTime? DeletedOn { get; set; } 
}

我个人喜欢这个想法,如果 CreatedOn == ModifiedOn,则表明它从未被修改过。有些人喜欢使用 DateTime?,但实际上它们的用途相同。
由于此示例在小项目中,我只是简单地封装了 EF 上下文,并没有使用任何 IoC/DI。这只是所有实际代码的一个小子集(一些方法和接口丢失了,但应该非常清晰)。
public sealed class MyDb : IDisposable
{
    private MyContext _context;

    public MyDb(string connectionString, Lazy<Guid?> currentUserIdFunc)
    {
        this._context = new MyContext(connectionString);

        Database.SetInitializer<MyContext>(new DatabaseInitializer());
        this._context.Database.Initialize(true);

        this._currentUserIdFunc = currentUserIdFunc;
    }

    public async Task<T> GetEntityAsync<T>(Func<IQueryable<T>, IQueryable<T>> entityQuery) where T : class, IDbEntity
    {
        var query = entityQuery(this._context.Set<T>());

        if (typeof(T) is IDbAuditable)
        {
            query = query.Cast<IDbAuditable>()
                 .Where(a => !a.DeletedById.HasValue)
                 .Cast<T>();
        }

        return await query.FirstOrDefaultAsync();
    }

    public async Task<int> UpdateAsync<T>(T entity) where T : class, IDbEntity
    {
        if (entity is IDbDoNotModify)
        {
            throw new DoNotModifyException("Entity cannot be Modified (IDoNotModify).");
        }

        this._context.Set<T>().Attach(entity);
        var entry = this._context.Entry<T>(entity);
        entry.State = EntityState.Unchanged;

        var entityType = entity.GetType();

        var metadata = entityType.GetCustomAttributes(typeof(MetadataTypeAttribute)).FirstOrDefault() as MetadataTypeAttribute;
        if (metadata != null)
        {

            var type = metadata.MetadataClassType;

            var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                 .Select(p => new
                 {
                     Name = p.Name,
                     ScaffoldColumn = p.GetCustomAttributes(typeof(ScaffoldColumnAttribute), true).FirstOrDefault() as ScaffoldColumnAttribute,
                     Readonly = entityType.GetProperty(p.Name).GetCustomAttributes(typeof(ReadOnlyAttribute), true).FirstOrDefault() as ReadOnlyAttribute
                 })
                 .Where(p => (p.ScaffoldColumn == null || p.ScaffoldColumn.Scaffold)
                     && (p.Readonly == null || !p.Readonly.IsReadOnly))
                 .ToList();

            foreach (var property in properties)
            {
                entry.Property(property.Name).IsModified = true;
            }

        }
        else
        {
            entry.State = EntityState.Modified;
        }

        var auditable = entity as IDbAuditable;
        if (auditable != null)
        {
            this.Modified(auditable, this._currentUserIdFunc.Value);
            entry.Property("ModifiedOn").IsModified = true;
            entry.Property("ModifiedById").IsModified = true;
        }


        return await this._context.SaveChangesAsync();
    }

    private void Modified(IDbAuditable instance, Guid? currentUserId)
    {
        instance.ModifiedById = currentUserId.Value;
        instance.ModifiedOn = DateTime.Now;
    }
}

这是我使用它的方法:
// returns the first car with a model of ford
var car = MyDb.EntityAsync<Car>((query) = query
  .Where(c => c.Model.Equals("ford")
);

// returns the first dog with an ownerId of id
var car = MyDb.EntityAsync<Dog>((query) => query
  .Where(d => d.OwnerId == Id)
);

UpdateAsync(car);

这个示例应用程序非常小,因此我没有实现任何UnitOfWork,但是它可以很容易地修改为这种情况。此外,如果您有多个上下文,MyDb类可以很容易地转换为MyDb<T>。我允许大量使用DataAnnotations。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接