Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map network type operations #407

Merged
merged 13 commits into from Jul 22, 2018

Conversation

austindrenski
Copy link
Contributor

@austindrenski austindrenski commented May 15, 2018

Map network type operations

See #299 for more information.

Operator Description Example
< is less than inet '192.168.1.5' < inet '192.168.1.6'
<= is less than or equal inet '192.168.1.5' <= inet '192.168.1.5'
= equals inet '192.168.1.5' = inet '192.168.1.5'
>= is greater or equal inet '192.168.1.5' >= inet '192.168.1.5'
> is greater than inet '192.168.1.5' > inet '192.168.1.4'
<> is not equal inet '192.168.1.5' <> inet '192.168.1.4'
<< is contained by inet '192.168.1.5' << inet '192.168.1/24'
<<= is contained by or equals inet '192.168.1/24' <<= inet '192.168.1/24'
>> contains inet '192.168.1/24' >> inet '192.168.1.5'
>>= contains or equals inet '192.168.1/24' >>= inet '192.168.1/24'
&& contains or is contained by inet '192.168.1/24' && inet '192.168.1.80/28'
~ bitwise NOT ~ inet '192.168.1.6'
& bitwise AND inet '192.168.1.6' & inet '0.0.0.255'
| bitwise OR inet '192.168.1.6' | inet '0.0.0.255'
+ addition inet '192.168.1.6' + 25
- subtraction inet '192.168.1.43' - 36
- subtraction inet '192.168.1.43' - inet '192.168.1.19'
Function Description
abbrev(inet) abbreviated display format as text
abbrev(cidr) abbreviated display format as text
broadcast(inet) broadcast address for network
family(inet) extract family of address; 4 for IPv4, 6 for IPv6
host(inet) extract IP address as text
hostmask(inet) construct host mask for network
masklen(inet) extract netmask length
netmask(inet) construct netmask for network
network(inet) extract network part of address
set_masklen(inet, int) set netmask length for inet value
set_masklen(cidr, int) set netmask length for cidr value
text(inet) extract IP address and netmask length as text
inet_same_family(inet, inet) are the addresses from the same family?
inet_merge(inet, inet) the smallest network which includes both of the given networks
trunc(macaddr) set last 3 bytes to zero
trunc(macaddr8) set last 5 bytes to zero
macaddr8_set7bit(macaddr8) set 7th bit to one, also known as modified EUI-64, for inclusion in an IPv6 address

@roji
Copy link
Member

roji commented May 15, 2018

@austindrenski, I know this PR is still in progress, but here's one thought... Unlike with the range operators, I think the inet operations shouldn't exist as a set of extension methods over IPAddress, but rather as extension methods on DbFunctions, requiring users to invoke them explicitly via EF.Functions.Contains().

The reason for this different is that IPAddress is a general .NET type that isn't used exclusively for database access, unlike ranges or the full text search types. It doesn't sound like a good idea for users to suddenly have IPAddress.Contains() appear just because they reference the Npgsql provider (not to mention that the method throws anywhere except for EF Core queries).

@austindrenski
Copy link
Contributor Author

Implementation considerations

@roji From looking at the basic operators, it seems feasible to provide local implementations.

My thought process was that if local implementations can be provided, and if those implementations are consistent with PostgreSQL, then extension methods would make sense.

On the technical side, I'm hoping that PostgreSQL network address operations will be easier to replicate than range operators. I could be wrong here. But as in #323, inconsistent local implementations would be worse than always-throw implementations.

Thinking about API consistency, I planned to follow the same implementation for NpgsqlInet and IPAddress (i.e. either both extension methods or both via DbFunctions).

What do you think? Do you foresee complications to the extension methods even if they support local evaluation?

NpgsqlInet and cidr mapping

I noticed that NpgsqlInet doesn't have an entry in NpgsqlTypeMappingSource.ClrTypeMappings, but there is a cidr mapping in NpgsqlTypeMappingSource.StoreTypeMappings.

For testing, I've added an entry for on this branch and it seems to work as expected.

Was this an oversight? Or is there a reason not to include the mapping?

@roji
Copy link
Member

roji commented May 16, 2018

My thought process was that if local implementations can be provided, and if those implementations are consistent with PostgreSQL, then extension methods would make sense.

I'm still very wary of adding extensions methods on standard, non-database-related types in an EF Core provider... A good example is the Like function provided by EF Core, which has a C# implementation but is still accessible as EF.Functions.Like() rather than as an extension method over string. The fear is that people start using the function without even being aware of where it's coming from (that's the danger of extensions methods), and at the end of the day we're a database provider, and not a general functionality provider...

(if you're interested we had a conversation about this with the EF Core team, I can dig it up).

On the technical side, I'm hoping that PostgreSQL network address operations will be easier to replicate than range operators. I could be wrong here. But as in #323, inconsistent local implementations would be worse than always-throw implementations.

I agree... I tend towards throwing in general, since IMHO there isn't a lot of value in the client implementation, and we have to be sure to provide a 100% accurate (and reasonably efficient) implementation - I'm not sure this is really worth our time. At the end of the day it's really up to you, but I'd recommend against it.

I noticed that NpgsqlInet doesn't have an entry in NpgsqlTypeMappingSource.ClrTypeMappings, but there is a cidr mapping in NpgsqlTypeMappingSource.StoreTypeMappings.

For testing, I've added an entry for on this branch and it seems to work as expected.

Was this an oversight? Or is there a reason not to include the mapping?

It's a bit of an oversight. Right now the so-called "provider-specific types" (NpgsqlInet, NpgsqlDateTime and the others) really supported in the EF Core provider - they're quite rarely-used. In most cases they only exist to provide access to the full range of precision/scale of the PostgreSQL type where the CLR type is more limited (e.g. PostgreSQL timestamp has 1 microsecond precision, but CLR DateTime has 10 microsecond precision). NpgsqlInet is a bit special because it bundles both an IP and a subnet, which is something that doesn't exist at all in CLR (so not just a precision difference).

Long story short, yes, I think it makes sense to have the mappings as follows:

  • IPAddress - inet (default CLR mapping)
  • NpgsqlInet -> cidr (default CLR mapping)
  • cidr -> NpgsqlInet (default store mapping
  • inet -> IPAddress (default store mapping)
  • inet -> NpgsqlInet (non-default store mapping, for people who want to use inet to represent a network - this is supported by PostgreSQL).

@austindrenski
Copy link
Contributor Author

To throw, or not to throw...

and at the end of the day we're a database provider, and not a general functionality provider...

Ahhh. That's a great point, and a helpful way to think about feature scope as a new contributor.

Perhaps that could be included in the refreshed documentation as the start of a contribution guide?

I'll take a look at the pattern for DbFunctions and update the network operators extensions to match.

Type mapping

Right now the so-called "provider-specific types" (NpgsqlInet, NpgsqlDateTime and the others) really supported in the EF Core provider - they're quite rarely-used.

This may be a silly question, but instead of default mapping cidr and NgpsqlInet, what do you think about simply setting the default mapping to a ValueTuple<IPAddress, int>?

It feels complicated to have one network type (inet) map to a built-in type, while a closely related network type (cidr) maps to a "provider-specific type".

Could something like this work?

  • CLR mapping:

    • Default:
      • IPAddress -> inet
      • (IPAddress Address, int Netmask) -> cidr
    • Non-default:
      • (IPAddress Address, int Netmask) -> inet
      • NpgsqlInet -> cidr
  • Store mapping:

    • Default:
      • cidr -> (IPAddress Address, int Netmask)
      • inet -> IPAddress
    • Non-default:
      • cidr -> NpgsqlInet
      • inet -> NpgsqlInet

Object slicing

On a related note, does mapping inet to IPAddress pose a risk for data loss/truncation?

@roji
Copy link
Member

roji commented May 17, 2018

This may be a silly question, but instead of default mapping cidr and NgpsqlInet, what do you think about simply setting the default mapping to a ValueTuple<IPAddress, int>?

It feels complicated to have one network type (inet) map to a built-in type, while a closely related network type (cidr) maps to a "provider-specific type".

I really like this idea. NpgsqlInet is almost the equivalent of an IPAdress/int tuple (it's a struct), it was created way before tuples existed.

The only catch is that this would need to be done at the ADO.NET level first and foremost. That's no big deal, I can do that soon, and at that point you can submit a PR for the corresponding changes in EF Core. Sounds good?

On a related note, does mapping inet to IPAddress pose a risk for data loss/truncation?

You mean because PostgreSQL inet can contain a subnet and IPAddress can't? That's true, but since we really had have two PG types, one really meant for doing networks (cidr) and mainstream use of inet is to hold a single IP, it seems like a shame to encumber the common simple IP scenario. Plus you always have the option of explicitly mapping to the tuple. So I think it should be ok...

@austindrenski
Copy link
Contributor Author

austindrenski commented May 17, 2018

Type mapping

The only catch is that this would need to be done at the ADO.NET level first and foremost. That's no big deal, I can do that soon, and at that point you can submit a PR for the corresponding changes in EF Core. Sounds good?

That works for me!

since we really had have two PG types, one really meant for doing networks (cidr) and mainstream use of inet is to hold a single IP, it seems like a shame to encumber the common simple IP scenario. Plus you always have the option of explicitly mapping to the tuple. So I think it should be ok...

I agree that it would impose a burden on the common IP use... But it does feel like the makings of a footgun situation... Would it introduce too much overhead to log some type of warning if this occurs during materialization?

For example, if the materialization is mapping inet -> IPAddress and it has subnet info, then log a warning of possible data loss.

If that's too much overhead (in general or relative to the risk), perhaps we can highlight the risk in the docs?

Question on parameter reuse and duplication

I'm seeing parameter duplication when the same NpgsqlInet is used multiple times in a query. There's also some convoluted equality handling...but that looks unrelated to the parameter duplication.

Is this expected behavior? Related to value types being copied on closure? Something else entirely?

Example:

[Fact]
public void Demonstrate_ValueTypeParametersAreDuplicated()
{
    using (NetContext context = Fixture.CreateContext())
    {
        NpgsqlInet npgsqlInet = new IPAddress(0);

        bool[] _ =
            context.NetTestEntities
                   .Where(x => EF.Functions.ContainsOrEquals(x.CidrMappedToNpgsqlInet, npgsqlInet))
                   .Select(x => x.CidrMappedToNpgsqlInet.Equals(npgsqlInet))
                   .ToArray();

        AssertContainsSql("SELECT x.\"CidrMappedToNpgsqlInet\" = @__npgsqlInet_0");
        AssertContainsSql("WHERE x.\"CidrMappedToNpgsqlInet\" >>= @__npgsqlInet_0");
    }
}

Expectation:

SELECT x."CidrMappedToNpgsqlInet" = @__npgsqlInet_0
FROM "NetTestEntities" AS x
WHERE x."CidrMappedToNpgsqlInet" >>= @__npgsqlInet_0 = TRUE

Reality:

SELECT CASE
    WHEN x."CidrMappedToNpgsqlInet" = @__npgsqlInet_1
    THEN TRUE::bool ELSE FALSE::bool
END
FROM "NetTestEntities" AS x
WHERE x."CidrMappedToNpgsqlInet" >>= @__npgsqlInet_0 = TRUE

austindrenski added a commit to austindrenski/npgsql-efcore.pg that referenced this pull request May 17, 2018
…xtensions to match NpgsqlDbFunctionsExtensions. Changed network operation extension methods to use DbFunctions instead of IPAddress/NpgsqlInet per the discussion in npgsql#407. Added XML docs to indicate that all of these extension methods throw a NotSupportedException outside of EF Core.
@roji
Copy link
Member

roji commented May 17, 2018

See #1937 for a PR for your changes at the ADO level, let me know what you think.

On a related note, does mapping inet to IPAddress pose a risk for data loss/truncation?

Regarding the data loss/truncation problem, I don't think there's actually a problem. The PostgreSQL docs say that "The inet type holds an IPv4 or IPv6 host address, and optionally its subnet". This is also shown by the fact that PostgreSQL doesn't force you to specify a subnet in the inet literal representation - you can put a simple IP and that's OK.

From the .NET perspective, it's a mater of what the user tells us they want. They always the option of saying they want the subnet - this is done by requesting an NpgsqlInet, or the tuple in the new proposed API. If they choose to read only an address, they are discarding the subnet part of it. This also happens to be the default behavior for the inet type, but I don't think that changes anything.

Note that this is very different from precision truncation, where, say, a database double loses precision when read... This is a column containing two distinct pieces of information, one of which is totally optional. So I think it's OK (maybe @YohDeadfall has an opinion).

I don't have time at the moment to look at the parameter reuse/duplication issue above, I'll try to do so soon.

austindrenski added a commit to austindrenski/npgsql-efcore.pg that referenced this pull request May 24, 2018
…xtensions to match NpgsqlDbFunctionsExtensions. Changed network operation extension methods to use DbFunctions instead of IPAddress/NpgsqlInet per the discussion in npgsql#407. Added XML docs to indicate that all of these extension methods throw a NotSupportedException outside of EF Core.
@austindrenski
Copy link
Contributor Author

I rebased the PR to the current dev and updated some NuGet settings to target the latest Npgsql build.

However, an exception is thrown if I attempt to construct test entities to add and save into the test context... If I comment out the local construct/add/save code, then the unit tests go back to passing. So the CLR to Postgres translation seems to be working fine.

It looks like the stack trace is complaining that tuples lack an == operator. Does Npgsql (or EF Core) require an == operator for something?

Do you have any insight into what might be causing this, @roji?

Npgsql.EntityFrameworkCore.PostgreSQL.Query.NetworkAddressQueryNpgsqlTest.IPAddressContainOrEqualIPAddress

System.AggregateException : One or more errors occurred. (The binary operator Equal is not defined for the types 'System.ValueTuple`2[System.Net.IPAddress,System.Int32]' and 'System.ValueTuple`2[System.Net.IPAddress,System.Int32]'.) (The following constructor parameters did not have matching fixture data: NetworkAddressQueryNpgsqlFixture fixture)
---- System.InvalidOperationException : The binary operator Equal is not defined for the types 'System.ValueTuple`2[System.Net.IPAddress,System.Int32]' and 'System.ValueTuple`2[System.Net.IPAddress,System.Int32]'.
---- The following constructor parameters did not have matching fixture data: NetworkAddressQueryNpgsqlFixture fixture

----- Inner Stack Trace #1 (System.InvalidOperationException) -----
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrAccessorFactory`1.Create(PropertyInfo propertyInfo, IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam,TValue](TValue& target, TParam param, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.ReadPropertyValue(IPropertyBase propertyBase)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.NetworkAddressQueryNpgsqlTest.NetworkAddressQueryNpgsqlFixture..ctor() in C:\Users\austin.drenski\Rider\Npgsql.EntityFrameworkCore.PostgreSQL\test\EFCore.PG.FunctionalTests\Query\NetworkAddressQueryNpgsqlTest.cs:line 270
----- Inner Stack Trace #2 (Xunit.Sdk.TestClassException) -----

@austindrenski austindrenski force-pushed the network-address-operators branch 4 times, most recently from eb49120 to 988b768 Compare May 24, 2018 22:22
@austindrenski
Copy link
Contributor Author

austindrenski commented May 24, 2018

Updates

b02bff3 completes the operators for inet and cidr.

I was adding exception messages to each function...but that started to look like a maintenance nightmare. So instead, I defined ClientEvalutaionNotSupportedException which extends NotSupportedException with a constructor that uses CallerMemberNameAttribute to forward the method's name to a templated message.

I had considered adding overloads for passing one inet and one cidr, but I think we can skip that since cidr is now explicitly a tuple of IPAddress and int. That should make it obvious (and trivial) for callers to mix and match inet and cidr for functions.

b860f0a completes the functions for inet and cidr.

8b5f37a completes the functions for macaddr and macaddr8.

  • Includes:
    • Tests for functions

1f23ca6 completes the tests for inet and cidr operators.

4f08f59 completes the operators for macaddr and macaddr8.

  • Includes relational and bitwise operators for macaddr and macaddr8.
  • Mapped to PhysicalAddress.
  • Includes functional tests covering new operators.

austindrenski added a commit to austindrenski/npgsql-efcore.pg that referenced this pull request May 25, 2018
…xtensions to match NpgsqlDbFunctionsExtensions. Changed network operation extension methods to use DbFunctions instead of IPAddress/NpgsqlInet per the discussion in npgsql#407. Added XML docs to indicate that all of these extension methods throw a NotSupportedException outside of EF Core.
austindrenski added a commit to austindrenski/npgsql-efcore.pg that referenced this pull request May 26, 2018
…xtensions to match NpgsqlDbFunctionsExtensions. Changed network operation extension methods to use DbFunctions instead of IPAddress/NpgsqlInet per the discussion in npgsql#407. Added XML docs to indicate that all of these extension methods throw a NotSupportedException outside of EF Core.
austindrenski added a commit to austindrenski/npgsql-efcore.pg that referenced this pull request May 26, 2018
@austindrenski austindrenski changed the title Map network address operations Map network type operations May 26, 2018
roji pushed a commit that referenced this pull request May 27, 2018
- Fix macaddr8 mapping
- Fix cidr mapping to (IPAddress, int)
- Split out from #407
@austindrenski austindrenski force-pushed the network-address-operators branch 2 times, most recently from 36e4961 to 748cea8 Compare July 21, 2018 20:24
austindrenski added a commit to austindrenski/npgsql-efcore.pg that referenced this pull request Jul 21, 2018
- Updated namespaces in extensions to match style.
- Changed network extensions to use DbFunctions per npgsql#407.
- Added XML docs to indicate these extensionss throw outside of EF Core.
austindrenski added a commit to austindrenski/npgsql-efcore.pg that referenced this pull request Jul 22, 2018
- Updated namespaces in extensions to match style.
- Changed network extensions to use DbFunctions per npgsql#407.
- Added XML docs to indicate these extensionss throw outside of EF Core.
@austindrenski
Copy link
Contributor Author

@roji I've given this another pass after #529. At this point, I think it's about as lean as it can be.

Any chance you have time to give it a last look? I would like to get it merged today, before I start breaking #431 into smaller PRs.

- cidr
- inet
- macaddr
- macaddr8
- Also some weird SQL being generated.
- Updated namespaces in extensions to match style.
- Changed network extensions to use DbFunctions per npgsql#407.
- Added XML docs to indicate these extensionss throw outside of EF Core.
- Updates:
  - Completed NpgsqlNetworkAddressTranslator switch.
  - Completed NpgsqlNetworkAddressExtensions for inet-inet and cidr-cidr.
  - Added ClientEvaluationNotSupportedException to throw from provider-specific extension methods.

- TODO:
  - Add non-operators for inet and cidr (i.e. functions).

- Problems:
  - Adding entities that have a tuple throws an exception related to a binary equality operator?
- Maps cidr and inet functions to CLR extension methods.
  - See: https://www.postgresql.org/docs/current/static/functions-net.html#CIDR-INET-FUNCTIONS-TABLE

- Includes functional tests covering translation.

- Includes the bug fix in npgsql#425.
  - This can be removed once npgsql#425 is merged.
- Maps macaddr and macaddr8 functions to CLR extension methods.
  - macaddr: https://www.postgresql.org/docs/current/static/functions-net.html#MACADDR-FUNCTIONS-TABLE
  - macaddr8: https://www.postgresql.org/docs/current/static/functions-net.html#MACADDR8-FUNCTIONS-TABLE

- Includes functional tests covering translation.

- TODO:
  - Map the standard relational operators and bitwise arithmetic operators.
- Includes functional tests convering network address operators.

- Includes the network function `ContainsOrContainedBy(...)`.

- Includes patch for npgsql#426

- Reordering functions/operators to reflect the docs for version 10.

- TODO:
  - Add operators for `macaddr` and `macaddr8`.
  - Add some basic info to the docs.
- Includes relational and bitwise operators for `macaddr` and `macaddr8`.
  - Mapped to `PhysicalAddress`.

- Includes functional tests covering new operators.

- TODO:
  - Add some basic info to the docs.

- BUG:
  - Tests failing for `macaddr8`:
```
System.FormatException : MAC addresses must have length 6 in PostgreSQL
  at Npgsql.TypeHandlers
           .NetworkHandlers
           .MacaddrHandler
           .ValidateAndGetLength(
               PhysicalAddress value,
               NpgsqlParameter parameter)
```
- Updates tests for new parentheses in custom binary operators.

- Renames `NetworkAddress*` to `Network*` to reflect the feature scope.

- Issues:
  - Tests for `macaddr8` are tricky.
    - Entity properties with column type `macaddr8` work fine.
    - But parameters of type `PhysicalAddress` are treated as a `macaddr`.
      - `System.FormatException : MAC addresses must have length 6 in PostgreSQL`
    - Perhaps Npgsql could detect and upgrade `PhysicalAddress` values to `macaddr8`?
      - But only when they have 8 bytes?
    - Opened issue 428 to discuss possible solutions.
- Adds missing tests for bitwise operators on `PhysicalAddress`.
  - For `macaddr` and `macaddr8`.

- Adds overloads mixing `inet` and `cidr` and related tests.
- ClientEvaluationNotSupportedException.cs
- NpgsqlRangeExtensions.cs
Copy link
Member

@roji roji left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at my last requests, it's really close to being mergeable, thanks!

/// This method is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static bool LessThan([CanBeNull] this DbFunctions _, IPAddress inet, IPAddress other)
=> throw ClientEvaluationNotSupportedException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed elsewhere, let's throw the standard NotSupportedException rather than this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually just a helper method now:

static NotSupportedException ClientEvaluationNotSupportedException(
    [CallerMemberName] string method = default)
    => new NotSupportedException($"{method} is only intended for use via SQL translation as part of an EF Core LINQ query.");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh ok...

/// <exception cref="NotSupportedException">
/// This method is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static bool Equal([CanBeNull] this DbFunctions _, IPAddress inet, IPAddress other)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this again, this isn't necessary... EF Core already translates x.Equals(y) to x = y in SQL.

So it should be fine to remove all Equal and NotEqual overloads below.

case nameof(NpgsqlNetworkExtensions.LessThanOrEqual):
return new CustomBinaryExpression(expression.Arguments[1], expression.Arguments[2], "<=", typeof(bool));

case nameof(NpgsqlNetworkExtensions.Equal):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, remove.

case nameof(NpgsqlNetworkExtensions.GreaterThan):
return new CustomBinaryExpression(expression.Arguments[1], expression.Arguments[2], ">", typeof(bool));

case nameof(NpgsqlNetworkExtensions.NotEqual):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, remove.

/// <summary>
/// Asserts that the SQL fragment appears in the logs.
/// </summary>
/// <param name="sql">The SQL statement or fragment
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: expression-bodied method

/// A <see cref="NetContext"/> for testing.
/// </returns>
public NetContext CreateContext()
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: expression-bodied method here and elsewhere

@austindrenski
Copy link
Contributor Author

@roji Just pushed d4d851f to address your review.

@roji
Copy link
Member

roji commented Jul 22, 2018

Thanks, LGTM.

Please squash everything, leaving a single concise commit...

@austindrenski austindrenski merged commit 4876d5e into npgsql:dev Jul 22, 2018
@austindrenski austindrenski deleted the network-address-operators branch July 22, 2018 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants