webForumDet fria alternativet

Entity framework och repositories.

.NETur .NET

15 svar · 864 visningar · startad av Fredde Mannen

Medlem sedan nov. 20014 054 inlägg
Frågan#1

Sitter och labbar lite med entity framework, och dyker på lite funderingar. Inga speciella stora svårigheter ännu, håller mig till rätt simpla saker.

Byggt ett baseobject för repositories med lite grundläggande funktionalitet:

Imports System.Linq.Expressions
Imports System.Data.Objects
Imports System.Configuration

Public MustInherit Class RepositoryBase(Of TEntity)
#Region "Mustoverride methods"
	Public MustOverride Sub MarkAsDeleted(ByVal entity As TEntity)
#End Region

#Region "Generall methods"
	Public Function GetAll() As IEnumerable(Of TEntity)
		Dim transaction As IDbTransaction = Context.Connection.BeginTransaction
		Dim entities As IEnumerable(Of TEntity) = Nothing

		Try
			entities = Context.CreateQuery(Of TEntity)(String.Format("[{0}]", GetType(TEntity).Name))
			transaction.Commit()

			Context.Connection.Close()
		Catch ex As Exception
			transaction.Rollback()
			Context.Connection.Close()
		End Try

		Return entities
	End Function

	Public Function GetByKey(ByVal where As Func(Of TEntity, Boolean)) As TEntity
		Dim entity As TEntity = Nothing
		Dim transaction As IDbTransaction = Context.Connection.BeginTransaction

		Try
			If Context.CreateQuery(Of TEntity)(String.Format("[{0}]", GetType(TEntity).Name)).Where(where).Count > 1 Then
				Throw New Exception("Not unique entity.")
			Else
				entity = Context.CreateQuery(Of TEntity)(String.Format("[{0}]", GetType(TEntity).Name)).Where(where).First
			End If

			transaction.Commit()

			Context.Connection.Close()
		Catch ex As Exception

			transaction.Rollback()
			Context.Connection.Close()
		End Try

		Return entity
	End Function

	Public Function GetByFilter(ByVal where As Func(Of TEntity, Boolean)) As IEnumerable(Of TEntity)
		Dim entities As IEnumerable(Of TEntity) = Nothing
		Dim transaction As IDbTransaction = Context.Connection.BeginTransaction

		Try
			entities = Context.CreateQuery(Of TEntity)(String.Format("[{0}]", GetType(TEntity).Name)).Where(where)

			transaction.Commit()

			Context.Connection.Close()
		Catch ex As Exception
			transaction.Rollback()
			Context.Connection.Close()
		End Try

		Return entities
	End Function
#End Region

#Region "Datacontext"
	Friend Function Context() As Domain.DataModelContainer
		Dim entityConnectionString As String = ConfigurationManager.ConnectionStrings("DataModelContainer").ConnectionString

		Dim connection As New EntityClient.EntityConnection(entityConnectionString)
		connection.Open()
		Return New Domain.DataModelContainer(connection)
	End Function
#End Region
End Class

Funkar bra än så länge i alla fall.. på test och labb nivå. :)

När man lägger till entiteter från databasen ex. Customers, Employees, Suppliers.

Så skapar designern exakt samma objekt.. Customers, Employees Suppliers..

Känns lite dumt bara, skulle vara bättre om det var som i Linq To Sql, att de då blir customer, employee och supplier.

Alternativ är ju att byta namn på dem i modellen eller i databasen, byter jag namn på dem i modellen så funkar givetvis inte min ovanstående kod.

Låter så dumt tycker jag:

Dim employee As New Employees

With employee
 .FirstName = "Kalle"
 .LastName = "Andersson
End employee

ge mig lite tankar och lite vägledning kanske, och ge gärna någon kommentar på koden i baseobjektet också. :)

Medlem sedan dec. 20014 239 inlägg
#2

Den här tråden kanske kan ge dig lite tips :)
http://www.webforum.nu/showthread.php?t=163666&highlight=entity

Medlem sedan nov. 20014 054 inlägg
#3

Zaiman skrev:

Den här tråden kanske kan ge dig lite tips :)
http://www.webforum.nu/showthread.php?t=163666&highlight=entity

Mja.. Det är ju Glads Entitymapper.

Min entitetsmappning sker ju via Entity Framework från Microsoft.

Kanske missar poängen! :)

Medlem sedan dec. 19996 522 inlägg
#4

OT: En vanlig förkommande grej med Repon är att det är mycket metoder i dem, mestadels för Find-saker, ett tips eftersom du kan linq:a är att helt bryta ut efter ett Finder-pattern eller implementera specification pattern.

Medlem sedan nov. 20014 054 inlägg
#5

erka skrev:

OT: En vanlig förkommande grej med Repon är att det är mycket metoder i dem, mestadels för Find-saker, ett tips eftersom du kan linq:a är att helt bryta ut efter ett Finder-pattern eller implementera specification pattern.

Funderade lite på det där med ett Specification-pattern.. har dock inte läst om det än.

Dock funderade jag på att ha mitt grundläggande finder metod i base:

Public Function GetByFilter(ByVal where As Func(Of TEntity, Boolean)) As IEnumerable(Of TEntity)
		Dim entities As IEnumerable(Of TEntity) = Nothing
		Dim transaction As IDbTransaction = Context.Connection.BeginTransaction

		Try
			entities = Context.CreateQuery(Of TEntity)(String.Format("[{0}]", GetType(TEntity).Name)).Where(where)

			transaction.Commit()

			Context.Connection.Close()
		Catch ex As Exception
			transaction.Rollback()
			Context.Connection.Close()
		End Try

		Return entities
	End Function

För att sedan i ex. EmployeeRepository

Public Function GetByFirstname(firstName As String) As IEnumrable(Of Domain.Employees)
  Return MyBase.GetByFilter(Function(e) e.FirstName.StartsWith(firstName))
End Function

Kan ju alltid labba lite med Specification-pattern också! :)

Medlem sedan dec. 19996 522 inlägg
#6

Egentligen kan du dra ut så dina finders enbart jobbar med linqfrågor, och på sättet isolera dem för testning effektivare. Det kräver dock att om du vill att repon ska dela på Findermetoder såsom ById etc. att du implementerar ett interface på de klasser som kan identifiera dem på Id utan reflection. Specification pattern är inte alltid rätt om du bara ska kunna hitta saker, utan kommer i sin rätt när du vill kombinera olika förutsättningar för att hitta något, eller då du har villkor som du inte har definierade i din domän via properties. Fowler har skrivit en bra PDF om specification pattern.

http://martinfowler.com/apsupp/spec.pdf

Du hittar en variant av finder som du kanske kan sno idér från på http://russelleast.wordpress.com/2008/09/20/implementing-the-repository-and-finder-patterns/, för nhibernate men principen är den samma :)

Medlem sedan nov. 20014 054 inlägg
#7

Laborerade vidare..

Laborerade lite vidare på detta med specifications.

Kom fram till detta:

Imports System.Linq.Expressions
Public Interface ISpecification(Of TEntity)
	ReadOnly Property Predicate() As Expression(Of Func(Of TEntity, Boolean))
	Function IsSatisfiedBy(ByVal entity As TEntity) As Boolean
End Interface
Imports System.Linq.Expressions
Public Class Specification(Of T)
	Implements ISpecification(Of T)

	Private ReadOnly _predicate As Expression(Of Func(Of T, Boolean))
	Public Sub New(ByVal predicate As Expression(Of Func(Of T, Boolean)))
		_predicate = predicate
	End Sub
	Public ReadOnly Property Predicate() As Expression(Of Func(Of T, Boolean)) Implements ISpecification(Of T).Predicate
		Get
			Return _predicate
		End Get
	End Property
	Public Function IsSatisfiedBy(ByVal entity As T) As Boolean Implements ISpecification(Of T).IsSatisfiedBy
		Return _predicate.Compile().Invoke(entity)
	End Function

	' Implementera en Or och en And tror jag.. 
End Class

Kastade in i mitt BaseRepository

	Protected Function GetBySpecification(ByVal where As ISpecification(Of TEntity)) As IEnumerable(Of TEntity)
		Dim entities As IEnumerable(Of TEntity) = Nothing
		Dim transaction As IDbTransaction = Context.Connection.BeginTransaction

		Try
			entities = Context.CreateQuery(Of TEntity)(String.Format("[{0}]", GetType(TEntity).Name)).Where(where.Predicate.Compile)

			transaction.Commit()

			Context.Connection.Close()
		Catch ex As Exception
			transaction.Rollback()
			Context.Connection.Close()
		End Try

		Return entities
	End Function

Exempel på en EmployeeSpecification.

Imports System.Linq.Expressions

Public Class EmployeeSpecification
	Public Shared ReadOnly Property DeletedEmployees() As Specification(Of Domain.Employees)
		Get
			Dim specification As Expression(Of Func(Of Domain.Employees, Boolean)) = Function(e) e.IsDeleted.Equals(True)
			Return New Specification(Of Domain.Employees)(specification)
		End Get
	End Property
End Class

Tror dock att jag skall ta och läsa lite på Fowler samt NHibernate implementeringen. Samt läsa på lite mer om de olika patterns som finns. :)

Men tycker det är nice övning för att lära sig Vb.Net bättre. :)

Medlem sedan dec. 19996 522 inlägg
#8

Läs och begrunda Fowler-artikeln, jag feltolkade också specificationpattern innan en vän tipsade mig om den artikeln och la ut hur jag hade feltolkat det :) Eller snarare, jag blev lite överkåt på det hehe, inte så många scenarion som gör spec.pattern nödvändigt, ofta bättre med finder, olika ändamål, men men :)

Medlem sedan maj 20012 812 inlägg
#9

En sak som jag gillar med Linq är deras IQueryable interface. Nu har jag inte implementerat det, men om jag byggde Repositorys på Linq/EF så skulle jag försöka få till en metod som kan ta emot ett "Linq statement" och exekvera det inne i repositoryn så att du får tillbaka en IEnumerable från metoden. Det hade varit smutt....

- M

Medlem sedan dec. 19996 522 inlägg
#10

I min värld går det lite emot tanken med Repositories, inom DDD ligger ansvaret att definera hur man kan kommunicera med sin domän, vad man kan få ut. Inte lägga ansvaret för hur och vad, på den som nyttjar repot. Vilket ditt förslag skulle innebära Gladh. Men så länge man inte exponerar en IQueryable<T> så kanske det inte bidrar med några drastiskt dåliga resultat :)

Freddemannen, kom att tänka på en sak som kan vara vettig för att förtydliga vart du kanske behöver specifications eller någon form av query-objekt. Låt oss säga att du har en property som räknas ut på ditt objekt, och du vill ställa en fråga som baseras på att denna property har ett visst värde. EF och Linq 2 SQL kommer då läsa upp alla dina objekt ur DB och sedan gör operationer på dessa för att hitta vilka som matchar. Det vill man undvika :)

Medlem sedan nov. 20014 054 inlägg
#11

erka skrev:

I min värld går det lite emot tanken med Repositories, inom DDD ligger ansvaret att definera hur man kan kommunicera med sin domän, vad man kan få ut. Inte lägga ansvaret för hur och vad, på den som nyttjar repot. Vilket ditt förslag skulle innebära Gladh. Men så länge man inte exponerar en IQueryable<T> så kanske det inte bidrar med några drastiskt dåliga resultat :)

Freddemannen, kom att tänka på en sak som kan vara vettig för att förtydliga vart du kanske behöver specifications eller någon form av query-objekt. Låt oss säga att du har en property som räknas ut på ditt objekt, och du vill ställa en fråga som baseras på att denna property har ett visst värde. EF och Linq 2 SQL kommer då läsa upp alla dina objekt ur DB och sedan gör operationer på dessa för att hitta vilka som matchar. Det vill man undvika :)

Btw. Läste lite från Fowler där han nämnde att det inte fanns några bra referenser till applikationer som nyttjats sig av Specifikation-pattern.

Dock däremot där jag tänker mig att man kan nyttja sig av en specifikation är kanske att jag ex en kunddatabas där vi har Guldkunder, Silverkund, Bronskunder osv. och deras status beror på hurlänge de varit kunder, eller antalet produkter de handlat genom tiden. Alternativen skulle kunna vara

customerRepository.GetByGoldStatus()
customerRepository.GetBySilverStatus()
customerRepository.GetByBronzeStatus()

eller kanske rent av implementera ett specifikation på dessa som man nyttjar på följande sätt.

customerRepository.GetAll(CustomerStatusSpecifikation.GoldCustomers)
customerRepository.GetAll(CustomerStatusSpecifikation.SilverCustomers)
customerRepository.GetAll(CustomerStatusSpecifikation.BronzeCustomers)

Hur ser ni på den tanken?

Medlem sedan maj 20012 812 inlägg
#12

erka skrev:

I min värld går det lite emot tanken med Repositories, inom DDD ligger ansvaret att definera hur man kan kommunicera med sin domän, vad man kan få ut. Inte lägga ansvaret för hur och vad, på den som nyttjar repot. Vilket ditt förslag skulle innebära Gladh

Tja ansvaret för vad som kommer att komma ur ditt repository kommer alltid att vila på den som anropar det, hur du än vrider och vänder på det. Och hur kan jag påstå detta då?

Enkelt! Du har följande repository.

public Customer GetCustomerById(int id){...}
public Customer GetCustomerByName(string name){...}
public Customer GetCustomerByCompany(Company company){...}

Det som kommer ut från detta repository beror ju på vad du stoppar in i parameterna. Så om du anropar GetCusomerById(3) så är det ju den anropande funktionen som sätter värdet 3 ;)

Och det var lite det som jag var uteefter även om jag inte beskrev det så klart, att det är inte hela LINQ satsen som jag skulle vilja skicka in, utan egentligen bara WHERE delen vilket ju så fall hade gett mig en metod att anropa istället för de 3 i ovanstående exempel.

Det hade blivit lite som Specifikation pattern, bara att min metod i repositoryn hade blivit fan så mycket mindre och enklare utan 58 miljoner vilkorsatser för att bygga ihop rätt statement i slutändan.

Så egentligen är det inte IQueryable interfacet som jag är ut efter. Jag vet vad du menar och har haft den diskutionen förut, och de motreaktioner man får på att använd sig av en IQueryable inne i sin kod är att du kanske inte får den data som du förväntar dig eftersom själva frågan körs inte i repositoryn utan "senare" i koden.

- M

Medlem sedan nov. 20014 054 inlägg
#13

Gladh skrev:

Det som kommer ut från detta repository beror ju på vad du stoppar in i parameterna. Så om du anropar GetCusomerById(3) så är det ju den anropande funktionen som sätter värdet 3 ;)

Och det var lite det som jag var uteefter även om jag inte beskrev det så klart, att det är inte hela LINQ satsen som jag skulle vilja skicka in, utan egentligen bara WHERE delen vilket ju så fall hade gett mig en metod att anropa istället för de 3 i ovanstående exempel.

Det hade blivit lite som Specifikation pattern, bara att min metod i repositoryn hade blivit fan så mycket mindre och enklare utan 58 miljoner vilkorsatser för att bygga ihop rätt statement i slutändan.

Så egentligen är det inte IQueryable interfacet som jag är ut efter. Jag vet vad du menar och har haft den diskutionen förut, och de motreaktioner man får på att använd sig av en IQueryable inne i sin kod är att du kanske inte får den data som du förväntar dig eftersom själva frågan körs inte i repositoryn utan "senare" i koden.

- M

Är inte lite som att skicka in en filtermetod liknande denna


Public Function GetByFilter(ByVal where As Func(Of TEntity, Boolean)) As IEnumerable(Of TEntity)
		Dim entities As IEnumerable(Of TEntity) = Nothing
		Dim transaction As IDbTransaction = Context.Connection.BeginTransaction

		Try
			entities = Context.CreateQuery(Of TEntity)(String.Format("[{0}]", GetType(TEntity).Name)).Where(where)

			transaction.Commit()

			Context.Connection.Close()
		Catch ex As Exception
			transaction.Rollback()
			Context.Connection.Close()
		End Try

		Return entities
	End Function

Där where satsen är en Lamda-sats.

Function(c) c.CustomerName.StartsWith("A")

Eller?
Dock kanske inte ultimat.. men åt det hållet?

Medlem sedan maj 20012 812 inlägg
#14

Jo typ något sådant hade varit underbart, hur det fungerar rent prestanda mässigt har jag ingen anning och dessutom så suger jag på VB.NET men det är något sådant som jag tänkt mig.

CreateQuery metoden är det någon egendefinerad eller till hör den någon OR-Mapper?

- M

Medlem sedan nov. 20014 054 inlägg
#15

Gladh skrev:

Jo typ något sådant hade varit underbart, hur det fungerar rent prestanda mässigt har jag ingen anning och dessutom så suger jag på VB.NET men det är något sådant som jag tänkt mig.

CreateQuery metoden är det någon egendefinerad eller till hör den någon OR-Mapper?

- M

Den tillhör en ORM-mapper.

Medlem sedan jan. 20022 440 inlägg
#16

Jag har en specification pattern jag sitter och pillar på ibland. Jag har NÄSTAN lyckats få till And & Or i Entity Framework men det vill sig inte riktigt än. Kanske skulle posta den ofärdig här så kan vi hjälpas åt att sätta ihop den?

Edit: Glömde säga att det är generiskt. Visst det håller inte i alla lägen men till enklare data access gör jag bara

ICriterion<User> criterion = new Criterion<User>(u => u.ID == 1);
var repository = new Repository();
var user = repository.GetBy(criterion);

Det jag saknar är att kunna lägga till & eller | och producera ett resultat på flera kriterium. Men det ska jag nog få till tids nog. Jäkligt smidig lösning i mindre projekt är det i alla fall :)

273 ms totalt · 3 externa anrop · v20260731065814-full.4bcf49fe
0 ms — hämta forumlista (cache)
128 ms — hämta statistik (db)
143 ms — hämta tråd, inlägg och bilagor (db)