I'm attempting to override the default behavior of the SQL migrations generator so that I may specify a custom foreign key constraint name, as discussed here. I've wired up the configuration as advised.
Unfortunately, however, it's not going so well.
A quick logging statement reveals that the GetFkName() function is never hit.
I tried an alternate configuration construct, as discussed here and here, but I'm getting this error when I attempt to generate a migration:
More than one migrations configuration type was found in the assembly 'ConsoleApp1'. Specify the name of the one to use.
I find this result a bit odd, as I have only one configuration class, one SQL generation class, and one context class (the code below doesn't reflect this, but I commented out the extras for my actual tests). Specifying the configuration type on the command line, as indicated here, errors with this:
System.InvalidOperationException: The type 'ConsoleApp1.Db.CustomDbConfiguration2' does not inherit from 'System.Data.Entity.DbConfiguration'. Entity Framework code-based configuration classes must inherit from 'System.Data.Entity.DbConfiguration'.
All of this brings us back here, then, which doesn't work for the aforementioned reason (GetFkName() never gets hit). So it seems I'm chasing my tail (didn't know I had one until today).
What should I do to get this override to work correctly?
Configuration
Imports System.Data.Entity
Imports System.Data.Entity.Migrations
Imports System.Data.Entity.SqlServer
Namespace Db
Friend Class CustomDbConfiguration
Inherits DbConfiguration
Public Sub New()
Me.SetMigrationSqlGenerator(SqlProviderServices.ProviderInvariantName, Function() New CustomSqlGenerator)
End Sub
End Class
Friend Class CustomDbConfiguration2
Inherits DbMigrationsConfiguration(Of Context)
Public Sub New()
Me.SetSqlGenerator(SqlProviderServices.ProviderInvariantName, New CustomSqlGenerator2(Me.GetSqlGenerator(SqlProviderServices.ProviderInvariantName)))
Me.ContextType = GetType(Context)
End Sub
End Class
End Namespace
SQL Generator
Imports System.Data.Entity.Migrations.Model
Imports System.Data.Entity.Migrations.Sql
Imports System.Data.Entity.SqlServer
Namespace Db
Friend Class CustomSqlGenerator
Inherits SqlServerMigrationSqlGenerator
Protected Overrides Sub Generate(AddForeignKeyOperation As AddForeignKeyOperation)
AddForeignKeyOperation.Name = GetFkName(AddForeignKeyOperation.PrincipalTable, AddForeignKeyOperation.DependentTable, AddForeignKeyOperation.DependentColumns.ToArray())
MyBase.Generate(AddForeignKeyOperation)
End Sub
Protected Overrides Sub Generate(DropForeignKeyOperation As DropForeignKeyOperation)
DropForeignKeyOperation.Name = GetFkName(DropForeignKeyOperation.PrincipalTable, DropForeignKeyOperation.DependentTable, DropForeignKeyOperation.DependentColumns.ToArray())
MyBase.Generate(DropForeignKeyOperation)
End Sub
Private Shared Function GetFkName(PrimaryKeyTable As String, ForeignKeyTable As String, ParamArray ForeignTableFields As String()) As String
IO.File.WriteAllText("D:\Logs\FkNameTest.log", $"{Now.ToString}{vbCrLf}")
Return $"FK_{ForeignKeyTable}_{PrimaryKeyTable}"
End Function
End Class
Friend Class CustomSqlGenerator2
Inherits MigrationSqlGenerator
Public Sub New(Generator As MigrationSqlGenerator)
Me.Generator = Generator
End Sub
Public Overrides Function Generate(MigrationOperations As IEnumerable(Of MigrationOperation), ProviderManifestToken As String) As IEnumerable(Of MigrationStatement)
Return Me.Generator.Generate(MigrationOperations, ProviderManifestToken)
End Function
Private ReadOnly Generator As MigrationSqlGenerator
End Class
End Namespace
Context
Imports System.Data.Common
Imports System.Data.Entity
Imports System.Data.SqlClient
Imports System.Reflection
Namespace Db
<DbConfigurationType(GetType(CustomDbConfiguration2))>
Friend Class Context
Inherits DbContext
Public Sub New()
MyBase.New(DbConnection.ConnectionString)
End Sub
Private Sub New(Connection As DbConnection)
MyBase.New(Connection, True)
Database.SetInitializer(New CreateDatabaseIfNotExists(Of Context))
Database.SetInitializer(New MigrateDatabaseToLatestVersion(Of Context, Migrations.Configuration))
Me.Database.Initialize(False)
End Sub
Public Shared Function Create() As Context
Return New Context(DbConnection)
End Function
Private Shared ReadOnly Property DbConnection As SqlConnection
Get
Return New SqlConnection(Utils.DbConnectionString)
End Get
End Property
Protected Overrides Sub OnModelCreating(Builder As DbModelBuilder)
Builder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly)
MyBase.OnModelCreating(Builder)
End Sub
Public Property Documents As DbSet(Of Document)
Public Property Sections As DbSet(Of Section)
End Class
End Namespace
Disclaimer: I haven't coded in VB for many years, these code examples are my feeble attempt to translate my working example in C# into OPs native VB. Please feel free to update my syntax ;)
You can manually edit the migration scripts to specify a custom name for each ForeignKey by specifying a value for the optional Name parameter in the call to Tablebuilder.ForeignKey as part of a create table statement:
CreateTable(
"dbo.CorporationVariety",
Function(c) New With
{
.Id = c.Int(nullable: false, identity:= true),
.CorporationId = c.Int(nullable:= false),
.VarietyId = c.Int(nullable:= false),
}) _
.PrimaryKey(Function(t) t.Id)
.ForeignKey("dbo.Corporation", Function(t) t.CorporationId, name := "FKCorporatationCorporationVarietyCorporationId")
.ForeignKey("dbo.Variety", Function(t) t.VarietyId, name := "FKVarietyCorporationVarietyVarietyId")
.Index(Function(t) t.CorporationId)
.Index(Function(t) t.VarietyId)
Or as part of a DbMigration.AddForeignKey statement:
AddForeignKey("dbo.CorporationVariety", "CorporationId", "dbo.Corporation", name := "FKCorporatationCorporationVarietyCorporationId")
AddForeignKey("dbo.CorporationVariety", "VarietyId", "dbo.Variety", name := "FKVarietyCorporationVarietyVarietyId")
If you have a lot of keys in your Model, and you want to implement a specific convention, (as in a standard rule or sequence of code that you want to apply in given scenarios) across all keys, then Normally the first place to look for a solution is EF Code First Conventions.
Unfortunately, there is neither a standard convention that can help you here nor can you define a custom name for a foreign key using fluent notation...
Normally we would go ahead and create a Custom Code First Convention to define your custom logic, this works in generally 2 ways:
Your convention executes standard configuration via Fluent Notation
we already noted that this option is not available to us...
Your convention logic stores custom metadata to the model via annotations
Primary and Foreign keys seem to be an anomaly in the EF Code First Runtime, there does not seem to be a way to easily access the annotations from the associations even though they are relatively easy to define.
I was surprised to find this and stumbled across this post that further confirms this: https://stackoverflow.com/a/54369685/1690217
Update I started this post assuming that Conventions was the right way to go, because I use it for many other customisations that I've needed to apply over the years. If you are looking to implement other similar types of customisations, look to Conventions first.
We can still easily override the standard VisualBasicMigrationCodeGenerator that generates the migration code files, so lets jump straight into that.
coapply to custom name for your ForeignKey and then implement a custom MigrationCodeGenerator to process the output from your convention.
Create a custom VisualBasicMigrationCodeGenerator
Register the Code Generator so that it is used by EF to generate the next migration
NOTE: This will not force existing keys in your database to be renamed. To do that you would need to force each key to be dropped and re-added back. For a large model Consider using a T4 template to create custom once-off migration logic to achieve this, once the above steps are in place.
Think of your Custom VisualBasicMigrationCodeGenerator as your personal EF code first sour dough culture, you can share this and re-use it for every new application, adding new functionality and improvements with each iteration. But Conventions are the configuration options that you may not want in every project, (which is why using _Conventions_ for OPs solution was my first direction.)
1. Create a custom VisualBasicMigrationCodeGenerator
Create a new class that inherits from the EF VisualBasicMigrationCodeGenerator, the minimal we need to do is override the AddForeignKeyOperation and modify the Name of the key and call the base implementation. This will affect all new keys added to the model.
To target keys added as part of CreateTable we will have to override GenerateInline(AddForeignKeyOperation...), however the base implemention (in the C# Generator...) doesn't obey the custom Name so instead we have to replace the implementation entirely.
When doing this, goto the EF project on GitHub and start with the original implementation, then inject your customizations as needed.
Please excuse this C#, I didn't have time to translate it, it does generate the correct VB code though ;)
public class CustomVBMigrationCodeGenerator : System.Data.Entity.Migrations.Design.VisualBasicMigrationCodeGenerator
{
protected override void Generate(AddForeignKeyOperation addForeignKeyOperation, IndentedTextWriter writer)
{
ApplyCustomFKName(addForeignKeyOperation);
base.Generate(addForeignKeyOperation, writer);
}
private void ApplyCustomFKName(ForeignKeyOperation operation)
{
// expecting FK without scheme or underscores: "FK{DependentTable}{PrincipalTable}{FKField}"
operation.Name = $"FK{StripSchemeFromName(operation.DependentTable)}{StripSchemeFromName(operation.PrincipalTable)}{String.Join("", operation.DependentColumns)}";
}
private string StripSchemeFromName(string dbObjectName)
{
return dbObjectName.Split(new[] { '.' }, 2).Last();
}
/// <summary>
/// Generates code to perform an <see cref="AddForeignKeyOperation" /> as part of a <see cref="CreateTableOperation" />.
/// </summary>
/// <param name="addForeignKeyOperation"> The operation to generate code for. </param>
/// <param name="writer"> Text writer to add the generated code to. </param>
protected virtual void GenerateInline(AddForeignKeyOperation addForeignKeyOperation, IndentedTextWriter writer)
{
// sourced from https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/Migrations/Design/VisualBasicMigrationCodeGenerator.cs
Check.NotNull(addForeignKeyOperation, "addForeignKeyOperation");
Check.NotNull(writer, "writer");
writer.WriteLine(" _");
writer.Write(".ForeignKey(" + Quote(addForeignKeyOperation.PrincipalTable) + ", ");
Generate(addForeignKeyOperation.DependentColumns, writer);
// Our Custom logic
ApplyCustomFKName(addForeignKeyOperation);
// Insert our custom name if provided
if (!addForeignKeyOperation.HasDefaultName)
{
writer.Write(", name := " + Quote(addForeignKeyOperation.Name));
}
if (addForeignKeyOperation.CascadeDelete)
{
writer.Write(", cascadeDelete := True");
}
writer.Write(")");
}
}
2. Register the Code Generator so that it is used by EF to generate the next migration
Locate Configuration.vb in your project, int the constructor set the CodeGenerator to an instance of your CustomVBMigrationCodeGenerator :
Public Sub New()
AutomaticMigrationsEnabled = false
CodeGenerator = new CustomVBMigrationCodeGenerator()
End Sub
Now execute the add-migration to generate a new migration you will see you new custom name defined in the migration script.
You may be required to similarly override the Generate(DropForeignKeyOperation...) methods as well if you need to downgrade from this configuration _or_ if alter table commands require the key to be dropped first.
Related
Say I have an object of my custom class, called AppSettings, which has various properties that hold both value types (integers, doubles, strings, etc.) and reference types (arrays, other custom objects, etc.). Some of these custom objects have their own custom objects, so the path down to some of the value type properties can go very deep.
For example:
<Serializable()>
Public Class AppSettings
Public Property windowHeight As Integer = 600
Public Property windowWidth As Integer = 800
Public Property defaultLengthUnit As Unit = Units.meters
Public Property defaultAngleUnit As Unit = Units.degrees
End Class
Where Unit class is defined as:
<Serializable()>
Public Class Unit
Public Property Name As String
Public Property Abbreviation As String
Public Property Scale As Double
End Class
And Units module is defined as:
Public Module Units
Public meters As New Unit With {
.Name = "Meters",
.Abbreviation = "m.",
.Scale = 1
}
Public degrees As New Unit With {
.Name = "Degrees",
.Abbreviation = "°",
.Scale = 1
}
End Module
Some other code might refer or bind to some of the reference type properties, or their internal properties. Now, let's say I provide a way for the user to save current state of AppSettings by serializing it into XML:
Public Sub SerializeAppSettings(ByVal filename As String)
Using sw As StreamWriter = New StreamWriter(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
xmls.Serialize(sw, appSettings)
End Using
End Sub
and then load them back (by deserializing) at any time while running the application:
Public Function DeserializeAppSettings(ByVal filename As String) As AppSettings
If Not File.Exists(filename) Then Return Nothing
Using sr As StreamReader = New StreamReader(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
Return TryCast(xmls.Deserialize(sr), AppSettings)
End Using
End Function
It is called like so:
AppSettings = DeserializeAppSettings(settingsFilePath)
The problem here is that all the references to AppSettings that other objects and bindings have, are now broken, because deserialization replaces the old instance of AppSettings with a completely new instance, and the references are not transferred to it.
It appears that this doesn't break references to value-type properties (like windowHeight, which is Integer), but it definitely breaks references to reference-type properties, like defaultLengthUnit. So for example, if some other object or WPF control is referring/binding to, say, AppSettings.defaultLengthUnit.scaleToBaseUnit, it doesn't work anymore.
I wonder, how can I fix this, so that deserialization would replace the old instance of AppSettings and transfer all the references from it to the new instance that it generated?
As I understand it, there are three ways to go about it:
Replace the old instance with an new one in the exact same memory allocation, with the same internal ID, which would probably be too hacky, and I'm not sure if at all possible.
Another way would be for the DeserializeAppSettings function to overwrite each property value of the current AppSettings instance, one by one, by the deserialized values. However, since some properties of AppSettings are objects, which have their own objects, which have their own objects (and so on), I would basically need to type out all the hierarchy tree in that DeserializeAppSettings function to get down to the value type properties. And every time I would need to add or remove any property in the AppSettings class (or in any class that is used in it's properties), I would also need to manually update the parsing code in DeserializeAppSettings function. This is seriously unmaintainable.
Lastly, it would probably be possible to automate this value replacement through reflection, but reflection is very slow, and generally discouraged if there is any other option.
I hope I am missing something obvious here. Any suggestions on how to transfer all the references to AppSettings when the old instance of it is replaced with a new one through deserialization?
EDIT: Updated the code to include all the relevant classes.
I need help converting some of this code. Mainly:
private static void SetProvider(ServiceCollection collection)
=> _service = collection.BuildServiceProvider();
and the line below it. This is being used for a discord bot using Discord.Net with the music library Victoria. Can someone also tell me what this actually is? Just a side question. this uses static classes and there's not anything called static on VB.Net so what would be the best call here? I've seen some other posts from here debating whether to use NonInheritable Class or a Module. What are the differences and when it is better to use either one?
It depends on what you want exactly. VB.NET does not provide static classes. Instead, it offers modules, but those are not completely equal to static classes.
The module version would be:
Public Module ServiceManager
Private _service As IServiceProvider
Public Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
Public Function GetService(Of T As New)() As T
Return _service.GetRequiredService(Of T)()
End Function
End Module
The class version would be:
Public NotInheritable Class ServiceManager
Private Sub New()
End Sub
Private Shared _service As IServiceProvider
Public Shared Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
Public Shared Function GetService(Of T As New)() As T
Return _service.GetRequiredService(Of T)()
End Function
End Class
When using the class implementation, you have to be careful to mark all members as Shared. Additionally, you can consider the following:
Declare the class as NotInheritable, since neither VB.NET modules nor C# static classes can be inherited from. (The corresponding C# keyword is sealed, by the way, but it will never be used in this context, since C# does support static classes.)
Create one private (default) constructor for the class. That will make sure that you cannot instantiate the class. VB.NET modules nor C# static classes cannot be instantiated either.
Using VB.NET modules is somewhat more straightforward, but keep in mind that VB.NET modules have a little quirk. When accessing a member of a module, you are typically not required to prefix it with the module name. Suppose you have some kind of service class called MyService and you have implemented your ServiceManager as a module. Then you do not need to call it like:
Dim svc As MyService = ServiceManager.GetService(Of MyService)()
Instead, you could just call it like:
Dim svc As MyService = GetService(Of MyService)()`.
When using the former method, Visual Studio actually suggests to simplify the name and change it to the latter method. But when you afterwards add another imported namespace that also happens to contain a module that has a GetService(Of T)() method, you will get an error indicating that GetService is ambiguous, in which case you would be forced to prefix it with the module name (like in the former method).
I personally find this checking behavior in Visual Studio regarding VB.NET module member usage to be rather annoying and confusing. I prefer prefixing calls with the module name (for the sake of writing self-documenting code and avoiding ambiguity as mentioned), but I do not want to disable the "simplify name" hint/suggestion in Visual Studio. So I personally prefer a class implementation instead of a module implementation when implementing something in VB.NET that mimics a C# static class.
Or even better: I would avoid a static class design and switch to a "regular" class design when possible. Using class instances has several advantages, like using composition (which is also an important technique used in many popular behavioral design patterns), simplified mocking/unittesting, and less side effects in general.
The equivalent VB.NET is:
Private Shared Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
C# expression bodies are just a single expression body method, MS Docs e.g. the following are equivalent:
void Greet()
{
Console.WriteLine("Hello World");
}
// Same as above
void Greet() => Console.WriteLine("Hello World");
I'm creating a webform vb.net project in visual studio 2012.
I am also using Entity Framework for managing my objects.
The big pain is caused by a View in my database, which I am forced to use.
I can retrieve the objects from this view with this class:
Imports System.ComponentModel.DataAnnotations
Public Class C
<Key, StringLength(6), Display(name:="C")> _
Public Property IDC() As String
<Display(Name:="Descrizione"), StringLength(255)> _
Public Property Descrizione() As String
End Class
Now I would like to create a brand new model called "S". Every object of type S should refer to a object, of type C, therefore my code is:
Imports System.ComponentModel.DataAnnotations
Public Class S
<Key, Display(name:="Id")> _
Public Property SID() As Integer
<Required, StringLength(255)>
Public Property Descrizione() As String
Public Overridable Property Commessa() As Commessa
End Class
Unfortunately, when I try to apply the migration, the process fails because tries to modify the View to enforce a contraint:
ALTER TABLE [dbo].[S] ADD CONSTRAINT [FK_dbo.Ss_dbo.cs_C_IDC] FOREIGN KEY ([C_IDC]) REFERENCES [dbo].[c] ([IDC])
The migration code is:
Public Overrides Sub Up()
CreateTable(
"dbo.Ss",
Function(c) New With
{
.SID = c.Int(nullable := False, identity := True),
.Descrizione = c.String(nullable := False, maxLength := 255),
.C_IDC = c.String(maxLength := 6)
}) _
.PrimaryKey(Function(t) t.SID) _
.ForeignKey("dbo.c", Function(t) t.C_IDC) _
.Index(Function(t) t.C_IDC)
End Sub
In the context class the correct View is defined:
Protected Overrides Sub OnModelCreating(ByVal modelBuilder As DbModelBuilder)
modelBuilder.Entity(Of C)().ToTable("c")
End Sub
Googling this problem around, I "think" a possibility is to turn off the cascade delete feature, but even with:
modelBuilder.Entity(Of S).HasRequired(Function(c) c.C).WithMany.WillCascadeOnDelete(False)
the migration tries to modify the View. (Is the code above correct?)
Is it possible to use the View in the Entity Framework in this way?
How can I link objects from the models I am going to create to elements in this View?
Is Entity Framework a reasonable approach in this scenario?
Thanks!
Code First Migrations is designed to automatically manage your database. If your DB has restrictions that make complete automation impossible (like Views), you could :
Disable Code First Migrations
Let EF generate a db update script for you, but only apply the parts you want
There is some great general info on using Code First Migrations with an existing DB on Ralph Lavelle's blog.
I am looking at creating a VB.NET 11 WPF MVVM application using Entity Framework 5 and Database First (Connecting to SQL Server 2008 R2).
I have chosen Database First, as I am migrating an existing solution to WPF MVVM, where the database already exists of course.
I'd like to begin using Dependency Injection so I can Unit Test as much of my code as possible.
I don't seem to be able to find a clear and concise walk-through of how to go about using Dependency Injection with EF DB-First, and in particular with vb.net. Although even a C# example would be fine I'm sure.
What I'd really like is a simple step by step guide explaining how to setup the solution, how to setup each part ready for Dependency Injection etc, but these seem hard to come by.
So far, I've created the Solution and it's Projects, as follows;
DBAccess - This houses nothing but my .edmx file, and a small mod to be able to supply the ConnectionString to the constructor.
DBControl - This houses the various classes which I use to provide a layer between my EDMX and my ViewModels. Specifically, I'm filling Complex Types (Which I have created using the designer) here for displaying "Friendlier" data via the UI, as well as converting these "Friendly" Complex Types to the mapped entities for saving / Updating. I have one class per table in my database. Each with two "FetchFriendlyRecords" methods (One accepts Filters) and an "AddUpdateFriendlyRecord" method. I have created an Interface for each class. Each class accepts a DbContext in it's constructor, and I'm simply passing my DBContext from the DBAccess Project.
MainUI - This houses my MVVM layers, and references each class in the DBControl Project in order to provide DataBinding etc.
I've seen suggested that, instead of spending time writing a complex solution to be able to unit test with EF, it's simpler to create a firm mock database with test data populated, and simply point the code at the mock database, rather than the live one. However, I'd prefer to be able to create an in memory solution that would run without any need to hit SQL Server at all.
Any help would be great, including telling me if I'm going about this all wrong!!
Update:
I have taken the solution provided by Paul Kirby below, and created a "Sort of" Repository Pattern I believe.
I created an interface;
Public Interface IFriendlyRepository(Of T)
ReadOnly Property FriendlyRecords As ObservableCollection(Of T)
Function GetFilteredFriendlyRecords(predicates As List(of Func(Of T, Boolean))) As ObservableCollection(Of T)
Function AddEditFriendlyRecord(ByVal RecordToSave As T) As EntityException
Sub SaveData()
End Interface
I then implemented this interface on a class by class basis;
Namespace Repositories
Public Class clsCurrenciesRepository
Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies)
Private _DBContext As CriticalPathEntities 'The Data Context
Public Sub New(ByVal Context As DbContext)
_DBContext = Context
End Sub
Public ReadOnly Property FriendlyRecords As ObservableCollection(Of FriendlyCurrencies) Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies).FriendlyRecords
Get
' We need to convert the results of a Linq to SQL stored procedure to a list,
' otherwise we get an error stating that the query cannot be enumerated twice!
Dim Query = (From Currencies In _DBContext.Currencies.ToList
Group Join CreationUsers In _DBContext.Users.ToList
On Currencies.CreationUserCode Equals CreationUsers.User_Code Into JoinedCreationUsers = Group
From CreationUsers In JoinedCreationUsers.DefaultIfEmpty
Group Join UpdateUsers In _DBContext.Users.ToList
On Currencies.LastUpdateUserCode Equals UpdateUsers.User_Code Into JoinedUpdateUsers = Group
From UpdateUsers In JoinedUpdateUsers.DefaultIfEmpty
Where (Currencies.Deleted = False Or Currencies.Deleted Is Nothing)
Order By Currencies.NAME
Select New FriendlyCurrencies With {.Currency_Code = Currencies.Currency_Code,
.NAME = Currencies.NAME,
.Rate = Currencies.Rate,
.CreatedBy = If(Currencies.CreationUserCode Is Nothing, "", CreationUsers.First_Name & " " & CreationUsers.Last_Name),
.CreationDate = Currencies.CreationDate,
.CreationUserCode = Currencies.CreationUserCode,
.Deleted = Currencies.Deleted,
.LastUpdateDate = Currencies.LastUpdateDate,
.LastUpdatedBy = If(Currencies.LastUpdateUserCode Is Nothing, "", UpdateUsers.First_Name & " " & UpdateUsers.Last_Name),
.LastUpdateUserCode = Currencies.LastUpdateUserCode}).ToList
Return New ObservableCollection(Of FriendlyCurrencies)(Query)
End Get
End Property
Public Function GetFilteredFriendlyRecords(predicates As List(of Func(Of FriendlyCurrencies, Boolean))) As ObservableCollection(Of FriendlyCurrencies) Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies).GetFilteredFriendlyRecords
Dim ReturnQuery = FriendlyRecords.ToList
For Each Predicate As Func(Of FriendlyCurrencies, Boolean) In predicates
If Predicate IsNot Nothing Then
ReturnQuery = ReturnQuery.Where(Predicate).ToList
End If
Next
Return New ObservableCollection(Of FriendlyCurrencies)(ReturnQuery)
End Function
Public Function AddEditFriendlyRecord(ByVal RecordToSave As FriendlyCurrencies) As EntityException Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies).AddEditFriendlyRecord
Dim dbCurrency As New Currency
' Check if this Staff Member Exists
Dim query = From c In _DBContext.Currencies
Where c.Currency_Code = RecordToSave.Currency_Code
Select c
' If Asset exists, then edit.
If query.Count > 0 Then
dbCurrency = query.FirstOrDefault
Else
'Do Nothing
End If
dbCurrency.Currency_Code = RecordToSave.Currency_Code
dbCurrency.NAME = RecordToSave.NAME
dbCurrency.CreationDate = RecordToSave.CreationDate
dbCurrency.CreationUserCode = RecordToSave.CreationUserCode
dbCurrency.LastUpdateDate = RecordToSave.LastUpdateDate
dbCurrency.LastUpdateUserCode = RecordToSave.LastUpdateUserCode
dbCurrency.Deleted = RecordToSave.Deleted
' Save Asset Object to Database
If query.Count > 0 Then
' If Asset exists, then edit.
Try
'_dbContext.SaveChanges 'We could save here but it's generally bad practice
Catch ex As EntityException
Return ex
End Try
Else
Try
_DBContext.Currencies.Add(dbCurrency)
'_dbContext.SaveChanges 'We could save here but it's generally bad practice
Catch ex As EntityException
Return ex
End Try
End If
Return Nothing
End Function
Public Sub SaveData() Implements Interfaces.IFriendlyRepository(Of CriticalPathDB.FriendlyCurrencies).SaveData
_DBContext.SaveChanges()
End Sub
End Class
End Namespace
I used constructor injection to insert the dbContext into the class.
I had hoped to be able to mock up a fake dbContext using my existing context and the "Effort" Unit Testing Tool.
However, I don't seem to be able to get this to work.
In the interim, in my Unit Test Project, I am dropping (If it already exists) and creating an empty test database, with the SQLCMD command, using the same schema as my live database.
I then create a dbContext referencing the Test Database, populate it with test data, and test against this.
As a note, I will be refactoring my "Add/Edit" method to work with an actual base Entity, rather than my "Friendly" complex version, this was the simplest method at the time.
If you're working DB-first, here's what I would suggest.
Open your .edmx file, right click on any blank space and choose "Add Code Generation Item"
In the "Online Templates" area, search for "EF 5.x DbContext Generator for VB".
Give the .tt file a name, hit add. This will change the way your .edmx file generates the backing code so that your entities are all POCO, which simplifies testing overall by keeping your main logic disconnected from EF.
After you've got that done, you probably want to look into something like the Unit of Work pattern. Here's a quick code example, I'll explain it after.
public interface IUnitOfWork
{
IDbSet<Location> Locations { get; }
void Commit();
}
public class EFUnitOfWork : IUnitOfWork
{
private readonly YourGeneratedDbContext _context;
public EFUnitOfWork(string connectionString)
{
_context = new YourGeneratedDbContext();
}
public IDbSet<Location> Locations
{
get { return _context.Locations; }
}
public void Commit()
{
_context.SaveChanges();
}
}
This is a basic unit of work that exposes some list of Locations as an example (sorry that it's in C# but I don't know VB well).
Notice that it is exposing IDbSet objects - this is where the magic comes in. If in your DBAccess project, you use this unit of work or a repository pattern to hide EF, and because it implements an interface and is returning IDbSet objects, anywhere that needs your data can have this IUnitOfWork constructor injected with DI, and replaced with a mocked version that returns mock IDbSet objects (they're just IQueryables in the end) when you need to unit test.
You may find that with the POCO generation in that new template, you can even do away with a lot of the work you're doing in your DBControl project.
Anyway that's just some basic stuff in terms of positioning your project for optimal unit testing and DI.
This is my first post on Stack Overflow so please exuse (and feel free to point out) any n00b mistakes.
I am trying to implement transactions across multiple TableAdapters in VB.NET (using Visual Studio 2010) by extending the partial class as described in the following examples:
http://blah.winsmarts.com/2006/06/18/the-definitive-tableadapters--transactions-blog-post.aspx
madprops.org/blog/typed-datasets-and-sqltransaction/
stackoverflow.com/questions/2342289/net-tableadapter-to-dataadapter
However, when I attempt to expose any of the private fields created by the designer they are underlined in the editor with the following error:
'_adapter' is not declared. It may be inaccessible due to its protection
level.
Searching this site as well as google has not revealed anything useful, but perhpas I'm searching the wrong keywords.
Here is the code in MyDataset.vb
Partial Public Class MyTableAdapter
Public Property MyTransaction() As SqlTransaction
Get
Return _adapter.SelectCommand.Transaction
End Get
Set(ByVal value As SqlTransaction)
If _adapter Is Nothing Then
InitAdapter()
End If
Connection = value.Connection
_adapter.InsertCommand.Transaction = value
_adapter.UpdateCommand.Transaction = value
_adapter.DeleteCommand.Transaction = value
End Set
End Property
End Class
and here is the designer code:
Partial Public Class MyTableAdapter
Inherits Global.System.ComponentModel.Component
Private WithEvents _adapter As Global.System.Data.SqlClient.SqlDataAdapter
Private _connection As Global.System.Data.SqlClient.SqlConnection
Private _transaction As Global.System.Data.SqlClient.SqlTransaction
Private _commandCollection() As Global.System.Data.SqlClient.SqlCommand
Am I missing something, or is this not possible in VB (all of the examples I've seen are in C#)?
TIA for any help!
JE
Can you access any Public properties/methods from the Adapter class?
I think you may have created your partial class in the wrong namespace. It has to be in Namespace MyDataSetTableAdapters