EntityFramework Core - migracja tworzy wszystkie pola z modelu, a nie wybrane

1

Cześć, to mój początek z EfCore (byłem już w nHibernate i Dapper, ale niestety w związku z wymogiem wieloplatformowości - XAMARIN, zostaje tylko EfCore - chyba, że chcę robić migracje bazy danych ręcznie sqlem. Ale wolałbym nie).

Mam taki problem. Mój model wygląda np. tak:

public class Document
{
   public Guid Id {get;set;}
   public string Name {get;set;}

   public int ValueIDontWantToBeInDb {get; set;}
}

Moje mapowanie wygląda tak:

public void CreateModel(ModelBuilder mb)
{
    var b = mb.Entity<Document>();

   b.ToTable("documents");
   b.Property(x => x.Id).ValueGeneratedOnAdd();
   b.HasKey(x => x.Id);
   b.Property(x => x.Name).IsRequired();
}

Jak widać, nigdzie nie umieszczam ValueIDontWantToBeInDb, a EfCore z uporem maniaka dorzuca mi to do schematu tabeli. Czemu tak jest i co zrobić, żeby tak nie było?

Znalazłem taką metodę jak Ignore, która mogłaby mi to załatwić, ale wtedy musiałbym te propertisy dopisywać do mapowania, a chcę tego uniknąć. Chcę, żeby w bazie danych były tylko te propertisy, które wskażę. Tak jak w nHibernate.

1

@Adam Boduch: błąd: No query results for model [Coyote\Post] 0 podczas dodawania komentarza do postu odpowiedzi.

@szydlak: Nie chciałbym używać Ignore.

0

To jedynie jeszcze atrybut. Wydaje mi się, że innej możliwości nie ma.
https://docs.microsoft.com/pl-pl/ef/core/modeling/included-properties

0

A gdyby za pomocą EF pobrać wszystkie nazwy column i walnąć ignore dynamicznie, a zostawić jakąś white liste? :D

modelBuilder.Entity<Contact>().Ignore("nazwa kolumny jako string");

0

Po to właśnie jest Ignore. Trzeba uzyc i nie szukac dziwnych sposobów.

0

czemu masz tam w ogóle property którego nie chcesz w bazie? rozumiem że fajnie mieć jeden obiekt do wszystkiego, od struktury danych w bazie, przez dao po view model po stronie klienta, no ale jednak sprawia to więcej problemów niż korzyści

0

OK, udało mi się rozpracować coś takiego. Choć uważam, że to obrzydliwe, że Ef nie ma tego w standardzie.

  • Tworzymy klasę bazową - BaseMap. I to w niej jest cała magia:
        abstract class BaseMap<T>: IEntityTypeConfiguration<T> where T: class, IDbItem //IDbItem to ograniczenie dla mnie, mój własny interfejs, który ma pole Id.
	{
		EntityTypeBuilder<T> theBuilder;
		List<string> mappedPropertyNames = new List<string>();

		protected PropertyBuilder<TProperty> Map<TProperty>(Expression<Func<T, TProperty>> x) //tego będziemy używać zamiast Property()
		{
			mappedPropertyNames.Add(GetPropertyName(x)); 
			return theBuilder.Property(x);
		}

		protected ReferenceNavigationBuilder<T, TRelatedEntity> HasOne<TRelatedEntity>(Expression<Func<T, TRelatedEntity>> x)
			where TRelatedEntity: class
		{
			mappedPropertyNames.Add(GetPropertyName(x));
			return theBuilder.HasOne(x);
		}

		protected CollectionNavigationBuilder<T, TRelatedEntity> HasMany<TRelatedEntity>(Expression<Func<T, IEnumerable<TRelatedEntity>>> x)
			where TRelatedEntity: class
		{
			mappedPropertyNames.Add(GetPropertyName(x));
			return theBuilder.HasMany(x);
		}

		protected PropertyBuilder<TColumnType> Map<TColumnType>(string propName)
		{
			mappedPropertyNames.Add(propName);
			return theBuilder.Property<TColumnType>(propName);
		}

		protected abstract void CreateModel(EntityTypeBuilder<T> builder);


		public void Configure(EntityTypeBuilder<T> builder)
		{
			theBuilder = builder;

			Map(x => x.Id).ValueGeneratedOnAdd();
			builder.HasKey(x => x.Id);

			CreateModel(builder);
			

			IgnoreUnmappedProperties(builder);
			
		}

		void IgnoreUnmappedProperties(EntityTypeBuilder<T> builder)
		{
			PropertyInfo[] propsInModel = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
			foreach (var prop in propsInModel)
			{
				if (!mappedPropertyNames.Contains(prop.Name))
				{
					builder.Ignore(prop.Name);
				}
			}
		}

		string GetPropertyName<TProperty>(Expression<Func<T, TProperty>> memberExpression)
		{
			MemberExpression member = null;

			switch (memberExpression.Body)
			{
				case UnaryExpression ue when ue.Operand is MemberExpression:
					member = ue.Operand as MemberExpression;
					break;

				case MemberExpression me:
					member = me;
					break;

				default:
					throw new InvalidOperationException("You should pass property to the method, for example: x => x.MyProperty");

			}

			var pInfo = member.Member as PropertyInfo;
			if (pInfo == null)
				throw new InvalidOperationException("You should pass property to the method, for example: x => x.MyProperty");

			return pInfo.Name;
		}
	}

Przykład klasy, która dziedziczy - czyli tworzy mapę dla faktycznego obiektu:

        class DocumentMap : BaseMap<Document>
	{
		protected override void CreateModel(EntityTypeBuilder<Document> b)
		{
			b.ToTable("documents");

			Map(x => x.Name).IsRequired();
			Map<Guid>("Owner_id").IsRequired();

			HasOne(x => x.Owner)
				.WithMany()
				.HasForeignKey("Owner_id")
				.HasConstraintName("FK_Users_Documents")
				.IsRequired()
				.OnDelete(DeleteBehavior.Cascade);

			HasMany(x => x.Periods)
				.WithOne(y => y.ParentDocument);

			HasMany(x => x.Loans)
				.WithOne(y => y.ParentDocument);
		}
	}

Zwróćcie uwagę, że zamiast metod z EntityTypeBuilder, używam tylko metod odziedziczonych po BaseMap. Na dobrą sprawę mógłbym tu w ogóle nie przekazywać EntityTypeBuilder.

A całość wywołujemy w DbContext:

                protected override void OnModelCreating(ModelBuilder builder)
		{
			base.OnModelCreating(builder);
			builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
		}

Dzięki takiemu rozwiązaniu mamy w bazie tylko te pola, które zdefiniujemy.

0

No a jaka jest przewaga takiego rozwiazania na ignore?

0

Wyobraź sobie, że dodajesz jakieś pole do modelu. Wszystko już oprogramowałeś, tylko zapomniałeś dodać Ignore'a do mapowania. I hop - masz w bazie pole, którego nie powinno być.

1 użytkowników online, w tym zalogowanych: 0, gości: 1